diff --git a/packages/custom-elements-json-core/src/ast/handleEvents.ts b/packages/custom-elements-json-core/src/ast/handleEvents.ts index 98150f6..9ffcdf4 100644 --- a/packages/custom-elements-json-core/src/ast/handleEvents.ts +++ b/packages/custom-elements-json-core/src/ast/handleEvents.ts @@ -1,4 +1,4 @@ -import ts from 'typescript'; +import ts, { isVariableDeclaration, isIdentifier, isCallExpression } from 'typescript'; import { Event, CustomElement } from 'custom-elements-json/schema'; import { customElementsJson } from '../customElementsJson'; import { extractJsDoc } from '../utils/extractJsDoc'; @@ -65,17 +65,25 @@ function visit(source: any, events: Event[]) { events.push(eventDoc); } - if (arg.kind === ts.SyntaxKind.Identifier) { + + if (isIdentifier(arg)) { customElementsJson.visitCurrentModule(node => { - switch (node.kind) { - case ts.SyntaxKind.Identifier: - if (node.parent.kind === ts.SyntaxKind.VariableDeclaration) { - if (node.getText() === arg.getText()) { - eventDoc.name = node.parent.initializer.arguments[0].text; - eventDoc.type = { type: node.parent.initializer.expression.getText() }; + if (isIdentifier(node)) { + if (isVariableDeclaration(node.parent)) { + if (node.getText() === arg.getText()) { + const initializer = node?.parent?.initializer; + if (initializer && isCallExpression(initializer)) { + // TODO: I didn't want to type this as any. + // But I think you might've made some assumptions + // that I don't understand by reading this code. + const firstArg = initializer.arguments?.[0] as any; + eventDoc.name = firstArg.text; + eventDoc.type = { type: initializer.expression?.getText() }; + events.push(eventDoc); } } + } } }); } diff --git a/packages/custom-elements-json-core/src/create.ts b/packages/custom-elements-json-core/src/create.ts index eacb9b8..de93219 100644 --- a/packages/custom-elements-json-core/src/create.ts +++ b/packages/custom-elements-json-core/src/create.ts @@ -1,27 +1,26 @@ -import path from 'path'; -import fs from 'fs'; -import globby from 'globby'; -import ts from 'typescript'; import { - Package, - Declaration, - JavaScriptModule, - CustomElement, - Export, Attribute, + ClassDeclaration, ClassMember, + CustomElement, + Declaration, Event, + Export, + JavaScriptModule, + Package, } from 'custom-elements-json/schema'; -import { ExportType, isValidArray, pushSafe } from './utils'; -import { Import, isBareModuleSpecifier } from './utils'; - -import { customElementsJson } from './customElementsJson'; +import fs from 'fs'; +import globby from 'globby'; +import path from 'path'; +import ts from 'typescript'; +import { getMixin } from './ast/getMixin'; import { handleClass } from './ast/handleClass'; import { handleCustomElementsDefine } from './ast/handleCustomElementsDefine'; import { handleExport } from './ast/handleExport'; import { handleImport } from './ast/handleImport'; -import { getMixin } from './ast/getMixin'; +import { customElementsJson } from './customElementsJson'; +import { ExportType, isBareModuleSpecifier, isImport } from './utils'; export async function create(packagePath: string): Promise { const modulePaths = await globby([`${packagePath}/**/*.js`]); @@ -65,92 +64,90 @@ export async function create(packagePath: string): Promise { */ // Match mixins with their imports - const classes = currModule.declarations.filter(declaration => declaration.kind === 'class'); + const classes = currModule.declarations.filter( + (declaration): declaration is ClassDeclaration => declaration.kind === 'class', + ); - classes.forEach((customElement: any) => { + + classes.forEach(customElement => { if (customElement.superclass && customElement.superclass.name !== 'HTMLElement') { - const foundSuperclass = [...(classes || []), ...(customElementsJson.imports || [])].find( - (_import: Import) => { - return _import.name === customElement.superclass.name; - }, - ); + const classesAndImports = [...(classes || []), ...(customElementsJson.imports || [])]; + const foundSuperclass = classesAndImports.find(_import => { + const superclassName = customElement?.superclass?.name; + return superclassName && _import.name === superclassName; + }); if (foundSuperclass) { - // Superclass is imported, but from a bare module specifier - if (foundSuperclass.kind && foundSuperclass.isBareModuleSpecifier) { - customElement.superclass.package = foundSuperclass.importPath; - } - - // Superclass is imported, but from a different local module - if (foundSuperclass.kind && !foundSuperclass.isBareModuleSpecifier) { - customElement.superclass.module = foundSuperclass.importPath; - } + if (isImport(foundSuperclass)) { + if (foundSuperclass.isBareModuleSpecifier) { + // Superclass is imported, but from a bare module specifier + customElement.superclass.package = foundSuperclass.importPath; + } else { + // Superclass is imported, but from a different local module + customElement.superclass.module = foundSuperclass.importPath; + } + } else { + // Superclass declared in local module - // Superclass declared in local module - if (foundSuperclass.isBareModuleSpecifier === undefined) { customElement.superclass.module = currModule.path; } } } - - customElement.mixins && - customElement.mixins.forEach((mixin: any) => { - const foundMixin = [ - ...(currModule.declarations || []), - ...(customElementsJson.imports || []), - ].find((_import: Import) => _import.name === mixin.name); - - if (foundMixin) { - /** - * Find a mixin's nested/inner mixins and add them to the class's list of mixins - * @example const MyMixin1 = klass => class MyMixin1 extends MyMixin2(klass) {} - */ - if (Array.isArray(foundMixin.mixins) && foundMixin.mixins.length > 0) { - foundMixin.mixins.forEach((mixin: any) => { - const nestedFoundMixin = [ - ...(currModule.declarations || []), - ...(customElementsJson.imports || []), - ].find((_import: Import) => _import.name === mixin.name); - - // Mixin is imported from a third party module (bare module specifier) - if (nestedFoundMixin.importPath && nestedFoundMixin.isBareModuleSpecifier) { - mixin.package = nestedFoundMixin.importPath; - } - - // Mixin is imported from a different local module - if (nestedFoundMixin.importPath && !nestedFoundMixin.isBareModuleSpecifier) { - mixin.module = nestedFoundMixin.importPath; - } - - // Mixin was found in the current modules declarations, so defined locally - if (!nestedFoundMixin.importPath) { + customElement.mixins?.forEach(mixin => { + const foundMixin = [ + ...(currModule.declarations || []), + ...(customElementsJson.imports || []), + ].find(_import => _import.name === mixin.name); + + + if (foundMixin) { + /** + * Find a mixin's nested/inner mixins and add them to the class's list of mixins + * @example const MyMixin1 = klass => class MyMixin1 extends MyMixin2(klass) {} + */ + if ('mixins' in foundMixin) { + foundMixin.mixins?.forEach((mixin: any) => { + const nestedFoundMixin = [ + ...(currModule.declarations || []), + ...(customElementsJson.imports || []), + ].find(_import => _import.name === mixin.name); + + if (isImport(nestedFoundMixin)) { + if (nestedFoundMixin.importPath) { + if (nestedFoundMixin.isBareModuleSpecifier) { + // Mixin is imported from a third party module (bare module specifier) + mixin.package = nestedFoundMixin.importPath; + } else { + // Mixin is imported from a different local module + mixin.module = nestedFoundMixin.importPath; + } + } else { + // Mixin was found in the current modules declarations, so defined locally mixin.module = currModule.path; } - - customElement.mixins.push(mixin); - }); - } + } + customElement.mixins = [...(customElement.mixins || []), mixin]; + }); + } + if (isImport(foundMixin)) { // Mixin is imported from bare module specifier - if (foundMixin.importPath && foundMixin.isBareModuleSpecifier) { + if (foundMixin.isBareModuleSpecifier) { mixin.package = foundMixin.importPath; - } - - // Mixin is imported from a different local module - if (foundMixin.importPath && !foundMixin.isBareModuleSpecifier) { + } else { + // Mixin is imported from a different local module mixin.module = foundMixin.importPath; } - - // Mixin was found in the current modules declarations, so defined locally - if (!foundMixin.importPath) { - mixin.module = currModule.path; - } } - }); + } else { + // Mixin was found in the current modules declarations, so defined locally + mixin.module = currModule.path; + } + }); }); // Find any mixins that were used in a class, so we can add them to a modules declarations - const usedMixins: any = []; - currModule.declarations.forEach((declaration: any) => { + const usedMixins: Declaration[] = []; + currModule.declarations.forEach(declaration => { if (declaration.kind === 'mixin') { // if its a mixin, find out if a class is making use of it const isUsed = currModule.declarations.find(nestedDeclaration => { @@ -259,44 +256,43 @@ export async function create(packagePath: string): Promise { if (klass.name === customElement.name) { return; } - - ['attributes', 'members', 'events'].forEach(type => { - klass[type] && - klass[type].forEach((currItem: Attribute | Event | ClassMember) => { - const moduleForKlass = customElementsJson.getModuleForClass(klass.name); - const moduleForMixin = customElementsJson.getModuleForMixin(klass.name); - - const newItem = { ...currItem }; - - /** - * If an attr, member or is already present in the base class, but we encounter it here, - * it means that the base has overridden that method from the super class, so we bail - */ - const itemIsOverridden = (customElement as any)[type]?.some( - (item: Attribute | Event | ClassMember) => newItem.name === item.name, - ); - if (itemIsOverridden) { - return; - } - - if (moduleForKlass && isBareModuleSpecifier(moduleForKlass)) { - newItem.inheritedFrom = { + // loop through attrs, events, members, add inherited from field, push to og class + klass.attributes && klass.attributes.forEach((attr: Attribute) => {}); + klass.events && klass.events.forEach((event: Event) => {}); + + klass.members && + klass.members.forEach((member: ClassMember) => { + const moduleForKlass = customElementsJson.getModuleForClass(klass.name); + let newMember; + + if (moduleForKlass && isBareModuleSpecifier(moduleForKlass)) { + newMember = { + ...member, + inheritedFrom: { name: klass.name, - package: moduleForKlass || moduleForMixin, - }; - } else { - newItem.inheritedFrom = { + package: moduleForKlass, + }, + }; + } else { + newMember = { + ...member, + inheritedFrom: { name: klass.name, - module: moduleForKlass || moduleForMixin, - }; - } - (customElement as any)[type] = pushSafe((customElement as any)[type], newItem); - }); - }); + module: moduleForKlass, + }, + }; + } + + if (Array.isArray(customElement.members) && customElement.members.length > 0) { + customElement.members.push(newMember); + } else { + customElement.members = [newMember]; + } + }); }); }); + customElementsJson.imports = []; - delete customElementsJson.imports; delete customElementsJson.currentModule; console.log(JSON.stringify(customElementsJson, null, 2)); diff --git a/packages/custom-elements-json-core/src/customElementsJson.ts b/packages/custom-elements-json-core/src/customElementsJson.ts index 57273ed..db5c64a 100644 --- a/packages/custom-elements-json-core/src/customElementsJson.ts +++ b/packages/custom-elements-json-core/src/customElementsJson.ts @@ -1,12 +1,16 @@ -import ts from 'typescript'; import { CustomElementsJson } from '@custom-elements-json/helpers'; +import ts from 'typescript'; import { Import } from './utils'; export class ExtendedCustomElementsJson extends CustomElementsJson { - currentModule: any; - imports: any; + currentModule: ts.Node | undefined; + imports: Import[]; + constructor() { + super(); + this.imports = []; + } - setCurrentModule(source: any) { + setCurrentModule(source: ts.Node) { this.currentModule = source; } @@ -14,13 +18,14 @@ export class ExtendedCustomElementsJson extends CustomElementsJson { this.imports = [...(this.imports || []), ...imports]; } - visitCurrentModule(cb: (node: any) => void) { - visitNode(this.currentModule); - + visitCurrentModule(cb: (node: ts.Node) => void) { function visitNode(node: ts.Node) { cb(node); ts.forEachChild(node, visitNode); } + if (this.currentModule) { + visitNode(this.currentModule); + } } } diff --git a/packages/custom-elements-json-core/src/utils/index.ts b/packages/custom-elements-json-core/src/utils/index.ts index 342bd9e..84ba57e 100644 --- a/packages/custom-elements-json-core/src/utils/index.ts +++ b/packages/custom-elements-json-core/src/utils/index.ts @@ -210,7 +210,16 @@ export function isBareModuleSpecifier(specifier: string): boolean { export interface Import { name: string; - kind: 'default' | 'named' | 'aggregate'; + kind: 'default' | 'named' | 'aggregate' | 'javascript-module'; importPath: string; isBareModuleSpecifier: boolean; } + +/** Type asserters */ +export function isImport(C: T | Import): C is Import { + if ('importPath' in C && 'isBareModuleSpecifier' in C) { + return true; + } + return false; +} + diff --git a/packages/custom-elements-json-helpers/index.mjs b/packages/custom-elements-json-helpers/index.mjs index 344678c..ee77571 100644 --- a/packages/custom-elements-json-helpers/index.mjs +++ b/packages/custom-elements-json-helpers/index.mjs @@ -33,3 +33,4 @@ export { isField, isMethod, }; + diff --git a/packages/custom-elements-json-helpers/src/helpers.ts b/packages/custom-elements-json-helpers/src/helpers.ts index 04c98eb..46736aa 100644 --- a/packages/custom-elements-json-helpers/src/helpers.ts +++ b/packages/custom-elements-json-helpers/src/helpers.ts @@ -39,6 +39,7 @@ export function isClass(item: Declaration | Export): boolean { return item.kind === 'class'; } + export function isMixin(item: Declaration | Export): boolean { return item.kind === 'mixin'; } diff --git a/packages/custom-elements-json/schema.ts b/packages/custom-elements-json/schema.ts index abf3601..bb3b6f0 100644 --- a/packages/custom-elements-json/schema.ts +++ b/packages/custom-elements-json/schema.ts @@ -131,6 +131,7 @@ export type Declaration = | FunctionDeclaration | MixinDeclaration | VariableDeclaration + | MixinDeclaration | CustomElement; /**