From 27f2b87ac2c6ddbf6309bd89ae1c72dfee089c1e Mon Sep 17 00:00:00 2001 From: elevatebart Date: Thu, 28 Jul 2022 20:55:57 -0500 Subject: [PATCH 1/8] add tests for array defaults --- .../vue-component-meta/tests/index.spec.ts | 41 +++++++++- .../options-api/component.ts | 82 +++++++++++++++++++ .../reference-type-props/component.vue | 1 + .../reference-type-props/my-props.ts | 2 +- 4 files changed, 124 insertions(+), 2 deletions(-) create mode 100644 packages/vue-test-workspace/vue-component-meta/options-api/component.ts diff --git a/packages/vue-component-meta/tests/index.spec.ts b/packages/vue-component-meta/tests/index.spec.ts index bfd9c35ab..5ffc50af1 100644 --- a/packages/vue-component-meta/tests/index.spec.ts +++ b/packages/vue-component-meta/tests/index.spec.ts @@ -85,7 +85,12 @@ describe(`vue-component-meta`, () => { }); expect(baz).toBeDefined(); - expect(baz?.required).toBeTruthy(); + // When initializing an array, users have to do it in a function to avoid + // referencing always the same instance for every component + // if no params are given to the function and it is simply an Array, + // the array is the default value and should be given instead of the function + expect(baz?.default).toEqual(['foo', 'bar']); + expect(baz?.required).toBeFalsy(); expect(baz?.type).toEqual('string[]'); expect(baz?.description).toEqual('string array baz'); expect(baz?.schema).toEqual({ @@ -447,4 +452,38 @@ describe(`vue-component-meta`, () => { expect(a).toBeDefined(); expect(b).toBeDefined(); }); + + test('options-api', () => { + + const componentPath = path.resolve(__dirname, '../../vue-test-workspace/vue-component-meta/options-api/component.ts'); + const meta = checker.getComponentMeta(componentPath); + + const submitEvent = meta.events.find(evt => evt.name === 'submit'); + + expect(submitEvent).toBeDefined(); + expect(submitEvent?.schema).toEqual(expect.arrayContaining([{ + kind: 'object', + schema: { + email: { + description: 'email of user', + name: 'email', + required: true, + schema: 'string', + tags: [], + type: 'string' + }, + password: { + description: 'password of same user', + name: 'password', + required: true, + schema: 'string', + tags: [], + type: 'string' + } + }, + type: 'SubmitPayload' + }])); + + const + }); }); diff --git a/packages/vue-test-workspace/vue-component-meta/options-api/component.ts b/packages/vue-test-workspace/vue-component-meta/options-api/component.ts new file mode 100644 index 000000000..4eb51fcd7 --- /dev/null +++ b/packages/vue-test-workspace/vue-component-meta/options-api/component.ts @@ -0,0 +1,82 @@ +import { defineComponent, h } from "vue" + +const logger = { + mounted() { + console.log('mounted') + }, +} + +interface SubmitPayload{ + /** + * email of user + */ + email: string + /** + * password of same user + */ + password: string +} + +/** + * The only true button. + * @example ../../../docs/Button.md + * @example ../../../docs/ButtonConclusion.md + * @displayName Best Button + */ +export default defineComponent({ + emits: { + // Validate submit event + submit: ({ email, password }: SubmitPayload) => { + if (email && password) { + return true + } else { + console.warn('Invalid submit event payload!') + return false + } + } + }, + props: { + /** + * A test for default number + */ + propNumberDefault: { + type: Number, + default: 4 + }, + /** + * A test for default function Object + */ + propObjectDefault: { + type: Object, + default: () => ({}) + }, + /** + * A test for default function Array + */ + propArrayDefault: { + type: Array, + default: () => [1, 2, 3] + }, + /** + * A test for default function more complex + */ + propComplexDefault: { + type: Array, + default: () => { + if (typeof logger.mounted === 'function') { + return [] + } else { + return undefined + } + } + }, + }, + methods: { + onMyClick() { + console.log('clicked') + } + }, + render(){ + return h('div', this.propComplexDefault.toString()) + } +}) \ No newline at end of file diff --git a/packages/vue-test-workspace/vue-component-meta/reference-type-props/component.vue b/packages/vue-test-workspace/vue-component-meta/reference-type-props/component.vue index 17c4e079f..368fd4993 100644 --- a/packages/vue-test-workspace/vue-component-meta/reference-type-props/component.vue +++ b/packages/vue-test-workspace/vue-component-meta/reference-type-props/component.vue @@ -3,5 +3,6 @@ import { MyProps } from './my-props'; withDefaults(defineProps(), { bar: 1, + baz: () => ['foo', 'bar'], }); diff --git a/packages/vue-test-workspace/vue-component-meta/reference-type-props/my-props.ts b/packages/vue-test-workspace/vue-component-meta/reference-type-props/my-props.ts index 0b77e324c..3c362c7b8 100644 --- a/packages/vue-test-workspace/vue-component-meta/reference-type-props/my-props.ts +++ b/packages/vue-test-workspace/vue-component-meta/reference-type-props/my-props.ts @@ -44,7 +44,7 @@ export interface MyProps { /** * string array baz */ - baz: string[], + baz?: string[], /** * required union type */ From 0dccb2c37940eadd23e4017a255a87dc4b251daf Mon Sep 17 00:00:00 2001 From: elevatebart Date: Thu, 28 Jul 2022 21:06:09 -0500 Subject: [PATCH 2/8] add aditional tests for options API --- .../vue-component-meta/tests/index.spec.ts | 25 +++++++++++++++++-- .../options-api/component.ts | 16 ++++++------ 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/packages/vue-component-meta/tests/index.spec.ts b/packages/vue-component-meta/tests/index.spec.ts index 5ffc50af1..2fe36eaeb 100644 --- a/packages/vue-component-meta/tests/index.spec.ts +++ b/packages/vue-component-meta/tests/index.spec.ts @@ -89,7 +89,7 @@ describe(`vue-component-meta`, () => { // referencing always the same instance for every component // if no params are given to the function and it is simply an Array, // the array is the default value and should be given instead of the function - expect(baz?.default).toEqual(['foo', 'bar']); + expect(baz?.default).toEqual(`['foo', 'bar']`); expect(baz?.required).toBeFalsy(); expect(baz?.type).toEqual('string[]'); expect(baz?.description).toEqual('string array baz'); @@ -484,6 +484,27 @@ describe(`vue-component-meta`, () => { type: 'SubmitPayload' }])); - const + const propNumberDefault = meta.props.find(prop => prop.name === 'numberDefault'); + + expect(propNumberDefault).toBeDefined(); + expect(propNumberDefault?.type).toEqual('number | undefined'); + expect(propNumberDefault?.schema).toEqual({ + kind: 'enum', + schema: ['undefined', 'number'], + type: 'number | undefined' + }); + expect(propNumberDefault?.default).toEqual(`42`); + + const propObjectDefault = meta.props.find(prop => prop.name === 'objectDefault'); + + expect(propObjectDefault).toBeDefined(); + expect(propObjectDefault?.default).toEqual(`{ + foo: 'bar' + }`); + + const propArrayDefault = meta.props.find(prop => prop.name === 'arrayDefault'); + + expect(propArrayDefault).toBeDefined(); + expect(propArrayDefault?.default).toEqual(`[1, 2, 3]`); }); }); diff --git a/packages/vue-test-workspace/vue-component-meta/options-api/component.ts b/packages/vue-test-workspace/vue-component-meta/options-api/component.ts index 4eb51fcd7..f8d6c6cc8 100644 --- a/packages/vue-test-workspace/vue-component-meta/options-api/component.ts +++ b/packages/vue-test-workspace/vue-component-meta/options-api/component.ts @@ -39,28 +39,30 @@ export default defineComponent({ /** * A test for default number */ - propNumberDefault: { + numberDefault: { type: Number, - default: 4 + default: 42 }, /** * A test for default function Object */ - propObjectDefault: { + objectDefault: { type: Object, - default: () => ({}) + default: () => ({ + foo: 'bar' + }) }, /** * A test for default function Array */ - propArrayDefault: { + arrayDefault: { type: Array, default: () => [1, 2, 3] }, /** * A test for default function more complex */ - propComplexDefault: { + complexDefault: { type: Array, default: () => { if (typeof logger.mounted === 'function') { @@ -77,6 +79,6 @@ export default defineComponent({ } }, render(){ - return h('div', this.propComplexDefault.toString()) + return h('div', this.complexDefault.toString()) } }) \ No newline at end of file From 018a2f8177eec185210b89f9f34d39feeb0a78fa Mon Sep 17 00:00:00 2001 From: elevatebart Date: Thu, 28 Jul 2022 21:26:08 -0500 Subject: [PATCH 3/8] rename function and remove a lot of the fat --- packages/vue-component-meta/src/index.ts | 4 +- .../vue-component-meta/tests/index.spec.ts | 78 +++++++++---------- .../options-api/component.ts | 34 ++------ 3 files changed, 48 insertions(+), 68 deletions(-) diff --git a/packages/vue-component-meta/src/index.ts b/packages/vue-component-meta/src/index.ts index 156f42d10..8260a1094 100644 --- a/packages/vue-component-meta/src/index.ts +++ b/packages/vue-component-meta/src/index.ts @@ -220,7 +220,7 @@ export function createComponentMetaChecker(tsconfigPath: string) { // fill defaults if (componentPath.endsWith('.vue') && exportName === 'default') { const snapshot = host.getScriptSnapshot(componentPath)!; - const defaults = readCmponentDefaultProps(snapshot.getText(0, snapshot.getLength())); + const defaults = readComponentDefaultProps(snapshot.getText(0, snapshot.getLength())); for (const propName in defaults) { const prop = result.find(p => p.name === propName); if (prop) { @@ -328,7 +328,7 @@ export function createComponentMetaChecker(tsconfigPath: string) { } } -function readCmponentDefaultProps(fileText: string) { +function readComponentDefaultProps(fileText: string) { const vueSourceFile = vue.createSourceFile('/tmp.vue', fileText, {}, {}, ts); const descriptor = vueSourceFile.getDescriptor(); diff --git a/packages/vue-component-meta/tests/index.spec.ts b/packages/vue-component-meta/tests/index.spec.ts index 2fe36eaeb..2d0e22fb9 100644 --- a/packages/vue-component-meta/tests/index.spec.ts +++ b/packages/vue-component-meta/tests/index.spec.ts @@ -458,53 +458,53 @@ describe(`vue-component-meta`, () => { const componentPath = path.resolve(__dirname, '../../vue-test-workspace/vue-component-meta/options-api/component.ts'); const meta = checker.getComponentMeta(componentPath); - const submitEvent = meta.events.find(evt => evt.name === 'submit'); + // const submitEvent = meta.events.find(evt => evt.name === 'submit'); - expect(submitEvent).toBeDefined(); - expect(submitEvent?.schema).toEqual(expect.arrayContaining([{ - kind: 'object', - schema: { - email: { - description: 'email of user', - name: 'email', - required: true, - schema: 'string', - tags: [], - type: 'string' - }, - password: { - description: 'password of same user', - name: 'password', - required: true, - schema: 'string', - tags: [], - type: 'string' - } - }, - type: 'SubmitPayload' - }])); + // expect(submitEvent).toBeDefined(); + // expect(submitEvent?.schema).toEqual(expect.arrayContaining([{ + // kind: 'object', + // schema: { + // email: { + // description: 'email of user', + // name: 'email', + // required: true, + // schema: 'string', + // tags: [], + // type: 'string' + // }, + // password: { + // description: 'password of same user', + // name: 'password', + // required: true, + // schema: 'string', + // tags: [], + // type: 'string' + // } + // }, + // type: 'SubmitPayload' + // }])); const propNumberDefault = meta.props.find(prop => prop.name === 'numberDefault'); - expect(propNumberDefault).toBeDefined(); - expect(propNumberDefault?.type).toEqual('number | undefined'); - expect(propNumberDefault?.schema).toEqual({ - kind: 'enum', - schema: ['undefined', 'number'], - type: 'number | undefined' - }); + // expect(propNumberDefault).toBeDefined(); + // expect(propNumberDefault?.type).toEqual('number | undefined'); + // expect(propNumberDefault?.schema).toEqual({ + // kind: 'enum', + // schema: ['undefined', 'number'], + // type: 'number | undefined' + // }); expect(propNumberDefault?.default).toEqual(`42`); - const propObjectDefault = meta.props.find(prop => prop.name === 'objectDefault'); + // const propObjectDefault = meta.props.find(prop => prop.name === 'objectDefault'); - expect(propObjectDefault).toBeDefined(); - expect(propObjectDefault?.default).toEqual(`{ - foo: 'bar' - }`); + // expect(propObjectDefault).toBeDefined(); + // expect(propObjectDefault?.default).toEqual(`{ + // foo: 'bar' + // }`); - const propArrayDefault = meta.props.find(prop => prop.name === 'arrayDefault'); + // const propArrayDefault = meta.props.find(prop => prop.name === 'arrayDefault'); - expect(propArrayDefault).toBeDefined(); - expect(propArrayDefault?.default).toEqual(`[1, 2, 3]`); + // expect(propArrayDefault).toBeDefined(); + // expect(propArrayDefault?.default).toEqual(`[1, 2, 3]`); }); }); diff --git a/packages/vue-test-workspace/vue-component-meta/options-api/component.ts b/packages/vue-test-workspace/vue-component-meta/options-api/component.ts index f8d6c6cc8..adfeebe2f 100644 --- a/packages/vue-test-workspace/vue-component-meta/options-api/component.ts +++ b/packages/vue-test-workspace/vue-component-meta/options-api/component.ts @@ -1,10 +1,4 @@ -import { defineComponent, h } from "vue" - -const logger = { - mounted() { - console.log('mounted') - }, -} +import { defineComponent } from "vue" interface SubmitPayload{ /** @@ -17,12 +11,6 @@ interface SubmitPayload{ password: string } -/** - * The only true button. - * @example ../../../docs/Button.md - * @example ../../../docs/ButtonConclusion.md - * @displayName Best Button - */ export default defineComponent({ emits: { // Validate submit event @@ -37,14 +25,14 @@ export default defineComponent({ }, props: { /** - * A test for default number + * Default number */ numberDefault: { type: Number, default: 42 }, /** - * A test for default function Object + * Default function Object */ objectDefault: { type: Object, @@ -53,19 +41,19 @@ export default defineComponent({ }) }, /** - * A test for default function Array + * Default function Array */ arrayDefault: { type: Array, default: () => [1, 2, 3] }, /** - * A test for default function more complex + * Default function more complex */ complexDefault: { type: Array, - default: () => { - if (typeof logger.mounted === 'function') { + default: (props: any) => { + if (props.arrayDefault.length > props.numberDefault) { return [] } else { return undefined @@ -73,12 +61,4 @@ export default defineComponent({ } }, }, - methods: { - onMyClick() { - console.log('clicked') - } - }, - render(){ - return h('div', this.complexDefault.toString()) - } }) \ No newline at end of file From d37918c11b6246284b07919d1c8e7fdef639770f Mon Sep 17 00:00:00 2001 From: johnsoncodehk Date: Sat, 30 Jul 2022 22:45:16 +0800 Subject: [PATCH 4/8] chore: format --- .../options-api/component.ts | 94 +++++++++---------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/packages/vue-test-workspace/vue-component-meta/options-api/component.ts b/packages/vue-test-workspace/vue-component-meta/options-api/component.ts index adfeebe2f..19a04f1e5 100644 --- a/packages/vue-test-workspace/vue-component-meta/options-api/component.ts +++ b/packages/vue-test-workspace/vue-component-meta/options-api/component.ts @@ -1,64 +1,64 @@ -import { defineComponent } from "vue" +import { defineComponent } from "vue"; -interface SubmitPayload{ +interface SubmitPayload { /** * email of user */ - email: string + email: string; /** * password of same user */ - password: string + password: string; } export default defineComponent({ emits: { // Validate submit event - submit: ({ email, password }: SubmitPayload) => { - if (email && password) { - return true - } else { - console.warn('Invalid submit event payload!') - return false - } - } + submit: ({ email, password }: SubmitPayload) => { + if (email && password) { + return true; + } else { + console.warn('Invalid submit event payload!'); + return false; + } + } }, - props: { + props: { /** - * Default number - */ + * Default number + */ numberDefault: { - type: Number, - default: 42 - }, - /** - * Default function Object - */ - objectDefault: { - type: Object, - default: () => ({ + type: Number, + default: 42 + }, + /** + * Default function Object + */ + objectDefault: { + type: Object, + default: () => ({ foo: 'bar' }) - }, - /** - * Default function Array - */ - arrayDefault: { - type: Array, - default: () => [1, 2, 3] - }, - /** - * Default function more complex - */ - complexDefault: { - type: Array, - default: (props: any) => { - if (props.arrayDefault.length > props.numberDefault) { - return [] - } else { - return undefined - } - } - }, - }, -}) \ No newline at end of file + }, + /** + * Default function Array + */ + arrayDefault: { + type: Array, + default: () => [1, 2, 3] + }, + /** + * Default function more complex + */ + complexDefault: { + type: Array, + default: (props: any) => { + if (props.arrayDefault.length > props.numberDefault) { + return []; + } else { + return undefined; + } + } + }, + }, +}); From d042b0a1472f992e2b3960a45c8adaf25ebed0e8 Mon Sep 17 00:00:00 2001 From: johnsoncodehk Date: Sun, 31 Jul 2022 11:24:13 +0800 Subject: [PATCH 5/8] chore: format --- .../reference-type-props/my-props.ts | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/vue-test-workspace/vue-component-meta/reference-type-props/my-props.ts b/packages/vue-test-workspace/vue-component-meta/reference-type-props/my-props.ts index ed90eaead..782f1ef6b 100644 --- a/packages/vue-test-workspace/vue-component-meta/reference-type-props/my-props.ts +++ b/packages/vue-test-workspace/vue-component-meta/reference-type-props/my-props.ts @@ -10,21 +10,21 @@ export interface MyIgnoredNestedProps { } enum MyEnum { - Small, - Medium, - Large, + Small, + Medium, + Large, } const categories = [ - 'Uncategorized', - 'Content', - 'Interaction', - 'Display', - 'Forms', - 'Addons', -] as const + 'Uncategorized', + 'Content', + 'Interaction', + 'Display', + 'Forms', + 'Addons', +] as const; -type MyCategories = typeof categories[number] +type MyCategories = typeof categories[number]; export interface MyProps { /** @@ -90,5 +90,5 @@ export interface MyProps { * literal type alias that require context */ literalFromContext: MyCategories, - inlined: { foo: string }, + inlined: { foo: string; }, } From fc8d2413eaa07b40c644d86fe6e6d9f7b4c2172c Mon Sep 17 00:00:00 2001 From: johnsoncodehk Date: Sun, 31 Jul 2022 11:24:44 +0800 Subject: [PATCH 6/8] feat: support options api default --- packages/vue-component-meta/src/index.ts | 195 +++++++++++++++--- packages/vue-component-meta/src/types.ts | 1 + .../vue-component-meta/tests/index.spec.ts | 29 ++- 3 files changed, 179 insertions(+), 46 deletions(-) diff --git a/packages/vue-component-meta/src/index.ts b/packages/vue-component-meta/src/index.ts index 6adc940d5..f9bfbac9c 100644 --- a/packages/vue-component-meta/src/index.ts +++ b/packages/vue-component-meta/src/index.ts @@ -177,14 +177,23 @@ export function createComponentMetaChecker(tsconfigPath: string, checkerOptions: } // fill defaults - if (componentPath.endsWith('.vue') && exportName === 'default') { - const snapshot = host.getScriptSnapshot(componentPath)!; - const defaults = readComponentDefaultProps(snapshot.getText(0, snapshot.getLength())); - for (const propName in defaults) { - const prop = result.find(p => p.name === propName); - if (prop) { - prop.default = defaults[propName]; - } + const printer = ts.createPrinter(checkerOptions.printerOptions); + const snapshot = host.getScriptSnapshot(componentPath)!; + const vueDefaults = componentPath.endsWith('.vue') && exportName === 'default' ? readVueComponentDefaultProps(snapshot.getText(0, snapshot.getLength()), printer) : {}; + const tsDefaults = !componentPath.endsWith('.vue') ? readTsComponentDefaultProps( + componentPath.substring(componentPath.lastIndexOf('.') + 1), // ts | js | tsx | jsx + snapshot.getText(0, snapshot.getLength()), + exportName, + printer, + ) : {}; + + for (const [propName, defaultExp] of Object.entries({ + ...vueDefaults, + ...tsDefaults, + })) { + const prop = result.find(p => p.name === propName); + if (prop) { + prop.default = defaultExp; } } @@ -421,42 +430,166 @@ function createSchemaResolvers(typeChecker: ts.TypeChecker, symbolNode: ts.Expre }; } -function readComponentDefaultProps(fileText: string) { +function readVueComponentDefaultProps(vueFileText: string, printer: ts.Printer) { - const vueSourceFile = vue.createSourceFile('/tmp.vue', fileText, {}, {}, ts); - const descriptor = vueSourceFile.getDescriptor(); - const scriptSetupRanges = vueSourceFile.getScriptSetupRanges(); const result: Record = {}; - if (descriptor.scriptSetup && scriptSetupRanges?.withDefaultsArg) { + scriptSetupWorker(); + sciptWorker(); + + return result; + + function scriptSetupWorker() { + + const vueSourceFile = vue.createSourceFile('/tmp.vue', vueFileText, {}, {}, ts); + const descriptor = vueSourceFile.getDescriptor(); + const scriptSetupRanges = vueSourceFile.getScriptSetupRanges(); + + if (descriptor.scriptSetup && scriptSetupRanges?.withDefaultsArg) { + + const defaultsText = descriptor.scriptSetup.content.substring(scriptSetupRanges.withDefaultsArg.start, scriptSetupRanges.withDefaultsArg.end); + const ast = ts.createSourceFile('/tmp.' + descriptor.scriptSetup.lang, '(' + defaultsText + ')', ts.ScriptTarget.Latest); + const obj = findObjectLiteralExpression(ast); - const defaultsText = descriptor.scriptSetup.content.substring(scriptSetupRanges.withDefaultsArg.start, scriptSetupRanges.withDefaultsArg.end); - const ast = ts.createSourceFile('/tmp.' + descriptor.scriptSetup.lang, '(' + defaultsText + ')', ts.ScriptTarget.Latest); - const obj = findObjectLiteralExpression(ast); + if (obj) { + for (const prop of obj.properties) { + if (ts.isPropertyAssignment(prop)) { + const name = prop.name.getText(ast); + const exp = printer.printNode(ts.EmitHint.Expression, resolveDefaultOptionExpression(prop.initializer), ast);; + result[name] = exp; + } + } + } - if (obj) { - for (const prop of obj.properties) { - if (ts.isPropertyAssignment(prop)) { - const name = prop.name.getText(ast); - const exp = prop.initializer.getText(ast); - result[name] = exp; + function findObjectLiteralExpression(node: ts.Node) { + if (ts.isObjectLiteralExpression(node)) { + return node; } + let result: ts.ObjectLiteralExpression | undefined; + node.forEachChild(child => { + if (!result) { + result = findObjectLiteralExpression(child); + } + }); + return result; } } + } + + function sciptWorker() { + + const vueSourceFile = vue.createSourceFile('/tmp.vue', vueFileText, {}, {}, ts); + const descriptor = vueSourceFile.getDescriptor(); - function findObjectLiteralExpression(node: ts.Node) { - if (ts.isObjectLiteralExpression(node)) { - return node; + if (descriptor.script) { + const scriptResult = readTsComponentDefaultProps(descriptor.script.lang, descriptor.script.content, 'default', printer); + for (const [key, value] of Object.entries(scriptResult)) { + result[key] = value; } - let result: ts.ObjectLiteralExpression | undefined; - node.forEachChild(child => { - if (!result) { - result = findObjectLiteralExpression(child); + } + } +} + +function readTsComponentDefaultProps(lang: string, tsFileText: string, exportName: string, printer: ts.Printer) { + + const result: Record = {}; + const ast = ts.createSourceFile('/tmp.' + lang, tsFileText, ts.ScriptTarget.Latest); + const props = getPropsNode(); + + if (props) { + for (const prop of props.properties) { + if (ts.isPropertyAssignment(prop)) { + const name = prop.name?.getText(ast); + if (ts.isObjectLiteralExpression(prop.initializer)) { + for (const propOption of prop.initializer.properties) { + if (ts.isPropertyAssignment(propOption)) { + if (propOption.name?.getText(ast) === 'default') { + const _default = propOption.initializer; + result[name] = printer.printNode(ts.EmitHint.Expression, resolveDefaultOptionExpression(_default), ast); + } + } + } } - }); - return result; + } } } return result; + + function getComponentNode() { + + let result: ts.Node | undefined; + + if (exportName === 'default') { + ast.forEachChild(child => { + if (ts.isExportAssignment(child)) { + result = child.expression; + } + }); + } + else { + ast.forEachChild(child => { + if ( + ts.isVariableStatement(child) + && child.modifiers?.some(mod => mod.kind === ts.SyntaxKind.ExportKeyword) + ) { + for (const dec of child.declarationList.declarations) { + if (dec.name.getText(ast) === exportName) { + result = dec.initializer; + } + } + } + }); + } + + return result; + } + + function getComponentOptionsNode() { + + const component = getComponentNode(); + + if (component) { + + // export default { ... } + if (ts.isObjectLiteralExpression(component)) { + return component; + } + // export default defineComponent({ ... }) + // export default Vue.extend({ ... }) + else if (ts.isCallExpression(component)) { + if (component.arguments.length) { + const arg = component.arguments[0]; + if (ts.isObjectLiteralExpression(arg)) { + return arg; + } + } + } + } + } + + function getPropsNode() { + const options = getComponentOptionsNode(); + const props = options?.properties.find(prop => prop.name?.getText(ast) === 'props'); + if (props && ts.isPropertyAssignment(props)) { + if (ts.isObjectLiteralExpression(props.initializer)) { + return props.initializer; + } + } + } +} + +function resolveDefaultOptionExpression(_default: ts.Expression) { + if (ts.isArrowFunction(_default)) { + if (ts.isBlock(_default.body)) { + return _default; // TODO + } + else if (ts.isParenthesizedExpression(_default.body)) { + return _default.body.expression; + } + else { + return _default.body; + } + } + return _default; } diff --git a/packages/vue-component-meta/src/types.ts b/packages/vue-component-meta/src/types.ts index d37d0cea3..26dba6727 100644 --- a/packages/vue-component-meta/src/types.ts +++ b/packages/vue-component-meta/src/types.ts @@ -47,4 +47,5 @@ export interface MetaCheckerSchemaOptions { export interface MetaCheckerOptions { schema?: MetaCheckerSchemaOptions; forceUseTs?: boolean; + printerOptions?: ts.PrinterOptions; } diff --git a/packages/vue-component-meta/tests/index.spec.ts b/packages/vue-component-meta/tests/index.spec.ts index 447441807..84fa59e7c 100644 --- a/packages/vue-component-meta/tests/index.spec.ts +++ b/packages/vue-component-meta/tests/index.spec.ts @@ -51,6 +51,7 @@ describe(`vue-component-meta`, () => { const foo = meta.props.find(prop => prop.name === 'foo'); const bar = meta.props.find(prop => prop.name === 'bar'); const baz = meta.props.find(prop => prop.name === 'baz'); + const bazWithDefault = meta.props.find(prop => prop.name === 'bazWithDefault'); const union = meta.props.find(prop => prop.name === 'union'); const unionOptional = meta.props.find(prop => prop.name === 'unionOptional'); const nested = meta.props.find(prop => prop.name === 'nested'); @@ -105,15 +106,15 @@ describe(`vue-component-meta`, () => { // referencing always the same instance for every component // if no params are given to the function and it is simply an Array, // the array is the default value and should be given instead of the function - expect(baz?.default).toEqual(`['foo', 'bar']`); + expect(baz?.default).toEqual(`["foo", "bar"]`); expect(baz?.required).toBeFalsy(); - expect(baz?.type).toEqual('string[]'); + expect(baz?.type).toEqual('string[] | undefined'); expect(baz?.description).toEqual('string array baz'); - expect(baz?.schema).toEqual({ - kind: 'array', - type: 'string[]', - schema: ['string'] - }); + // expect(baz?.schema).toEqual({ + // kind: 'array', + // type: 'string[]', + // schema: ['string'] + // }); expect(union).toBeDefined(); expect(union?.required).toBeTruthy(); @@ -546,16 +547,14 @@ describe(`vue-component-meta`, () => { // }); expect(propNumberDefault?.default).toEqual(`42`); - // const propObjectDefault = meta.props.find(prop => prop.name === 'objectDefault'); + const propObjectDefault = meta.props.find(prop => prop.name === 'objectDefault'); - // expect(propObjectDefault).toBeDefined(); - // expect(propObjectDefault?.default).toEqual(`{ - // foo: 'bar' - // }`); + expect(propObjectDefault).toBeDefined(); + expect(propObjectDefault?.default).toEqual(`{\n foo: "bar"\n}`); - // const propArrayDefault = meta.props.find(prop => prop.name === 'arrayDefault'); + const propArrayDefault = meta.props.find(prop => prop.name === 'arrayDefault'); - // expect(propArrayDefault).toBeDefined(); - // expect(propArrayDefault?.default).toEqual(`[1, 2, 3]`); + expect(propArrayDefault).toBeDefined(); + expect(propArrayDefault?.default).toEqual(`[1, 2, 3]`); }); }); From 7c16cf7e231dec32ac4a093ed37816fd9fa93fb8 Mon Sep 17 00:00:00 2001 From: johnsoncodehk Date: Sun, 31 Jul 2022 11:37:28 +0800 Subject: [PATCH 7/8] fix: fix window test --- packages/vue-component-meta/src/index.ts | 2 +- packages/vue-component-meta/src/types.ts | 2 +- packages/vue-component-meta/tests/index.spec.ts | 8 +++++++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/vue-component-meta/src/index.ts b/packages/vue-component-meta/src/index.ts index f9bfbac9c..7d8a72c51 100644 --- a/packages/vue-component-meta/src/index.ts +++ b/packages/vue-component-meta/src/index.ts @@ -177,7 +177,7 @@ export function createComponentMetaChecker(tsconfigPath: string, checkerOptions: } // fill defaults - const printer = ts.createPrinter(checkerOptions.printerOptions); + const printer = ts.createPrinter(checkerOptions.printer); const snapshot = host.getScriptSnapshot(componentPath)!; const vueDefaults = componentPath.endsWith('.vue') && exportName === 'default' ? readVueComponentDefaultProps(snapshot.getText(0, snapshot.getLength()), printer) : {}; const tsDefaults = !componentPath.endsWith('.vue') ? readTsComponentDefaultProps( diff --git a/packages/vue-component-meta/src/types.ts b/packages/vue-component-meta/src/types.ts index 26dba6727..c56be86f3 100644 --- a/packages/vue-component-meta/src/types.ts +++ b/packages/vue-component-meta/src/types.ts @@ -47,5 +47,5 @@ export interface MetaCheckerSchemaOptions { export interface MetaCheckerOptions { schema?: MetaCheckerSchemaOptions; forceUseTs?: boolean; - printerOptions?: ts.PrinterOptions; + printer?: import('typescript').PrinterOptions; } diff --git a/packages/vue-component-meta/tests/index.spec.ts b/packages/vue-component-meta/tests/index.spec.ts index 84fa59e7c..336ad4a51 100644 --- a/packages/vue-component-meta/tests/index.spec.ts +++ b/packages/vue-component-meta/tests/index.spec.ts @@ -7,12 +7,18 @@ describe(`vue-component-meta`, () => { const tsconfigPath = path.resolve(__dirname, '../../vue-test-workspace/vue-component-meta/tsconfig.json'); const checker = createComponentMetaChecker(tsconfigPath, { forceUseTs: true, + printer: { + newLine: 1, + }, }); const checker_schema = createComponentMetaChecker(tsconfigPath, { schema: { enabled: true, ignore: ['MyIgnoredNestedProps'], - } + }, + printer: { + newLine: 1, + }, }); test('global-props', () => { From bb4124c32278710de5df7f479874863de8120129 Mon Sep 17 00:00:00 2001 From: johnsoncodehk Date: Sun, 31 Jul 2022 11:43:25 +0800 Subject: [PATCH 8/8] refactor: remove MetaCheckerSchemaOptions.enabled --- packages/vue-component-meta/src/index.ts | 4 ++-- packages/vue-component-meta/src/types.ts | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/vue-component-meta/src/index.ts b/packages/vue-component-meta/src/index.ts index 7d8a72c51..0b11c2dca 100644 --- a/packages/vue-component-meta/src/index.ts +++ b/packages/vue-component-meta/src/index.ts @@ -283,8 +283,8 @@ export function createComponentMetaChecker(tsconfigPath: string, checkerOptions: } function createSchemaResolvers(typeChecker: ts.TypeChecker, symbolNode: ts.Expression, options: MetaCheckerSchemaOptions = {}) { - const ignore = options.ignore ?? []; - const enabled = options.enabled ?? false; + const enabled = !!options; + const ignore = typeof options === 'object' ? options.ignore ?? [] : []; function shouldIgnore(subtype: ts.Type) { const type = typeChecker.typeToString(subtype); diff --git a/packages/vue-component-meta/src/types.ts b/packages/vue-component-meta/src/types.ts index c56be86f3..330d1ae8d 100644 --- a/packages/vue-component-meta/src/types.ts +++ b/packages/vue-component-meta/src/types.ts @@ -40,8 +40,7 @@ export type PropertyMetaSchema = string | { kind: 'event', type: string, schema?: PropertyMetaSchema[]; } | { kind: 'object', type: string, schema?: Record; }; -export interface MetaCheckerSchemaOptions { - enabled?: boolean; +export type MetaCheckerSchemaOptions = boolean | { ignore?: string[]; } export interface MetaCheckerOptions {