Skip to content

Commit

Permalink
Merge pull request #9 from jaredmcateer/vuex-class
Browse files Browse the repository at this point in the history
  • Loading branch information
jaredmcateer authored May 19, 2022
2 parents ab54d52 + 726925a commit 39c3999
Show file tree
Hide file tree
Showing 18 changed files with 894 additions and 31 deletions.
46 changes: 29 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,16 @@ The files to be converted must meet the criteria below:
- [ ] `@ProvideReactive / @InjectReactive`
- [x] `@Emit`
- [x] `@Ref`
- vuex-class
- [x] `@Action`
- [x] `@Getter`
- [x] `@Mutation`
- [x] `@State`
- `@<namespace>`
- [ ] `.Action`
- [ ] `.Getter`
- [ ] `.Mutation`
- [ ] `.State`
- [x] replace `this` to `props`, `variable`, or `context`.
- [x] sort by dependency.

Expand Down Expand Up @@ -113,24 +123,26 @@ const { file, result } = convertFile(

```typescript
{
// root path for calc file absolute path, if in CLI, --root value will replace. default:`process.cwd()`
root?: string
// show debug message. default: `false`
debug?: boolean,
// if true, use @vue/composition-api. default: `false`
compatible?: boolean
// first setup function parameter name. default: `props`
setupPropsKey?: string
// second setup function parameter name. default: `context`
setupContextKey?: string
// Use custom version typescript. default: Typescript 3.7.3
typescript?: typeof ts
// Use custom version vue-template-compiler, please match your project vue versions. default: vue-template-compiler 2.6.11
vueTemplateCompiler?: typeof vueTemplateCompiler
// Use custom prettier config file path. if file does not exist, use default uncouth prettier config. default: `.prettierrc`
prettierConfig?: string
// root path for calc file absolute path, if in CLI, --root value will replace.
root?: string, // Default:`process.cwd()`
// show debug message.
debug?: boolean, // Default: `false`
// if true, use @vue/composition-api.
compatible?: boolean, // Default: `false`
// first setup function parameter name.
setupPropsKey?: string, // Default: `props`
// second setup function parameter name.
setupContextKey?: string, // Default: `context`
// Use custom version typescript.
typescript?: typeof ts, // Default: Typescript 3.7.3
// Use custom version vue-template-compiler, please match your project vue versions.
vueTemplateCompiler?: typeof vueTemplateCompiler, // Default: vue-template-compiler 2.6.11
// Use custom prettier config file path. if file does not exist, use default uncouth prettier config.
prettierConfig?: string // Default: `.prettierrc`
// Use custom ASTConvertPlugins for ASTConvert and ASTTransform
plugins?: ASTConvertPlugins
plugins?: ASTConvertPlugins,
// if using vuex-class the variable name to use for the store
vuexKey?: string, // Default: `store`
}
```

Expand Down
2 changes: 1 addition & 1 deletion src/convert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { runPlugins } from "./plugins";
import { UncouthOptions } from "./options";
import { log } from "./debug";

const vueClassModules = ["vue-class-component", "vue-property-decorator"];
const vueClassModules = ["vue-class-component", "vue-property-decorator", "vuex-class"];

export function convertAST(
sourceFile: ts.SourceFile,
Expand Down
22 changes: 20 additions & 2 deletions src/helpers/TsHelper.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import ts from "typescript";
import { UncouthOptions } from "../options";
import { ImportModule } from "../plugins/types";
import { isString } from "../utils";

export class TsHelper {
Expand All @@ -15,11 +16,28 @@ export class TsHelper {
return this.module.factory;
}

namedImports(names: string[]): { named: string[]; external: string }[] {
get rocketToken(): ts.PunctuationToken<ts.SyntaxKind.EqualsGreaterThanToken> {
return this.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken);
}

getDecorator(node: ts.Node, decorator: string): ts.Decorator | undefined {
if (!node?.decorators) return;

return node.decorators.find(
(el) => (el.expression as ts.CallExpression).expression.getText() === decorator
);
}

getVueExternal(): "@vue/composition-api" | "vue" {
return this.compatible ? "@vue/composition-api" : "vue";
}

namedImports(names: string[], external?: string): ImportModule[] {
external = external || this.getVueExternal();
return [
{
named: names,
external: this.compatible ? "@vue/composition-api" : "vue",
external,
},
];
}
Expand Down
2 changes: 2 additions & 0 deletions src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export interface UncouthOptions {
typescript: typeof ts;
vueTemplateCompiler: typeof vueTemplateCompiler;
prettierConfig: string;
vuexKey: string;
plugins: ASTConvertPlugins;
}

Expand All @@ -28,6 +29,7 @@ export function getDefaultUncouthOptions(tsModule: typeof ts = ts): UncouthOptio
vueTemplateCompiler: vueTemplateCompiler,
prettierConfig: ".prettierrc",
plugins: getDefaultPlugins(tsModule),
vuexKey: "store",
};
}

Expand Down
87 changes: 81 additions & 6 deletions src/plugins/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ import { convertInject } from "./vue-property-decorator/Inject";
import { convertProvide } from "./vue-property-decorator/Provide";
import { convertTemplateRef } from "./vue-class-component/TemplateRef";
import { TsHelper } from "../helpers/TsHelper";
import { convertVuexAction } from "./vuex-class/Action";
import { convertVuexGetter } from "./vuex-class/Getter";
import { convertVuexState } from "./vuex-class/State";
import { convertVuexMutation } from "./vuex-class/Mutation";

export function getDefaultPlugins(tsModule: typeof ts): ASTConvertPlugins {
return {
Expand All @@ -41,6 +45,10 @@ export function getDefaultPlugins(tsModule: typeof ts): ASTConvertPlugins {
convertInject,
convertData,
convertTemplateRef,
convertVuexAction,
convertVuexGetter,
convertVuexState,
convertVuexMutation,
],
[tsModule.SyntaxKind.GetAccessor]: [convertGetter],
[tsModule.SyntaxKind.SetAccessor]: [convertSetter],
Expand Down Expand Up @@ -132,6 +140,8 @@ export function convertASTResultToSetupFn(
): ts.MethodDeclaration {
const $t = new TsHelper(options);

const composables = getComposables(astResults, $t);

const returnStatement = $t.addTodoComment(
$t.factory.createReturnStatement(
$t.factory.createObjectLiteralExpression([
Expand All @@ -149,6 +159,7 @@ export function convertASTResultToSetupFn(
"setup",
[options.setupPropsKey, options.setupContextKey],
[
...composables,
...(astResults
.filter((el) => el.kind === ASTResultKind.COMPOSITION)
.map((el) => el.nodes)
Expand All @@ -158,15 +169,74 @@ export function convertASTResultToSetupFn(
);
}

interface Clause {
named: Set<string>;
default?: string;
params?: ts.Expression[];
}

function getComposables(astResults: ASTResult<ts.Node>[], $t: TsHelper): ts.VariableStatement[] {
const composableMap = new Map<string, Clause>();
astResults.forEach((result) => {
if (!result.composables) return;

result.composables.forEach((info) => {
const func = info.func;
const tmp: Clause = composableMap.get(func) ?? { named: new Set() };

if (!("default" in tmp) && "default" in info) {
tmp.default = info.default;
}

if ("params" in info) {
tmp.params = info.params;
}

if ("named" in info) {
info.named?.forEach((name) => tmp.named.add(name));
}

composableMap.set(func, tmp);
});
});

const composables = Array.from(composableMap)
.map(([func, clause]) => {
const funcExpr = $t.createCallExpression(func, undefined, clause.params);
const statements: ts.VariableStatement[] = [];
if (clause.default) {
statements.push($t.createConstStatement(clause.default, funcExpr));
} else {
const u = undefined;
const importElements = [...clause.named].map((name) =>
$t.factory.createBindingElement(u, u, $t.factory.createIdentifier(name))
);

if (importElements.length > 0) {
const vars = $t.factory.createObjectBindingPattern(importElements);
const boundElementsDeclaration = $t.factory.createVariableDeclaration(
vars,
u,
u,
funcExpr
);
const varDecList = $t.factory.createVariableDeclarationList([boundElementsDeclaration]);
statements.push($t.factory.createVariableStatement(u, varDecList));
}
}

return statements;
})
.flat()
.filter((composable): composable is ts.VariableStatement => !!composable);

return composables;
}

export function convertASTResultToImport(
astResults: ASTResult<ts.Node>[],
options: UncouthOptions
): ts.ImportDeclaration[] {
interface Clause {
named: Set<string>;
default?: string;
}

const $t = new TsHelper(options);

const importMap = new Map<string, Clause>();
Expand All @@ -177,10 +247,15 @@ export function convertASTResultToImport(
if (!("default" in temp) && "default" in importInfo) {
temp.default = importInfo.default;
}
temp.named.add("defineComponent");

if (key === "vue") {
temp.named.add("defineComponent");
}

for (const named of importInfo.named || []) {
temp.named.add(named);
}

importMap.set(key, temp);
}
}
Expand Down
12 changes: 12 additions & 0 deletions src/plugins/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,24 @@ export type ImportModule =
external: string;
};

export type ComposableStatement =
| {
default?: string;
func: string;
params?: ts.Expression[];
}
| {
named?: string[];
func: string;
params?: ts.Expression[];
};
export interface ASTResultBase {
imports: ImportModule[];
kind: ASTResultKind;
reference: ReferenceKind;
attributes: string[];
tag: string;
composables?: ComposableStatement[];
}

export interface ASTResultToObject<N = ts.PropertyAssignment> extends ASTResultBase {
Expand Down
14 changes: 14 additions & 0 deletions src/plugins/vuex-class/Action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { convertVuexMethodFactory } from "./convertMethodFactory";

/**
* @example
* \@Action('myNamespace/someAction') doSomeAction: (text: string) => Promise<void>
* // converts to
* const doSomeAction = async (text: string): Promise<void> => {
* return await store.dispatch('myNamespace/someAction', text);
* }
* @param node
* @param options
* @returns
*/
export const convertVuexAction = convertVuexMethodFactory("Action", "dispatch");
11 changes: 11 additions & 0 deletions src/plugins/vuex-class/Getter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { convertVuexComputedFactory } from "./convertComputedFactory";

/**
* @example
* \@Getter('myNamespace/someGetter') getSomething: boolean;
* // converts to
* const getSomething = computed<boolean>(() => {
* return store.getter('myNamespace/someGetter');
* })
*/
export const convertVuexGetter = convertVuexComputedFactory("Getter", "getters");
14 changes: 14 additions & 0 deletions src/plugins/vuex-class/Mutation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { convertVuexMethodFactory } from "./convertMethodFactory";

/**
* @example
* \@Mutation('myNamespace/SOME_MUTATION') mutateSomething: (text: string) => void
* // converts to
* const mutateSomething = (text: string): void => {
* return store.commit('myNamespace/SOME_MUTATION', text);
* }
* @param node
* @param options
* @returns
*/
export const convertVuexMutation = convertVuexMethodFactory("Mutation", "commit");
11 changes: 11 additions & 0 deletions src/plugins/vuex-class/State.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { convertVuexComputedFactory } from "./convertComputedFactory";

/**
* @example
* \@State('myNamespace/stateA') sA: number;
* // converts to
* const sA = computed(() => {
* return store.state.stateA;
* });
*/
export const convertVuexState = convertVuexComputedFactory("State", "state");
Loading

0 comments on commit 39c3999

Please sign in to comment.