Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 16 additions & 8 deletions packages/custom-elements-json-core/src/ast/handleEvents.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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);
}
}
}
}
});
}
Expand Down
224 changes: 110 additions & 114 deletions packages/custom-elements-json-core/src/create.ts
Original file line number Diff line number Diff line change
@@ -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<Package> {
const modulePaths = await globby([`${packagePath}/**/*.js`]);
Expand Down Expand Up @@ -65,92 +64,90 @@ export async function create(packagePath: string): Promise<Package> {
*/

// 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 => {
Expand Down Expand Up @@ -259,44 +256,43 @@ export async function create(packagePath: string): Promise<Package> {
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));
Expand Down
19 changes: 12 additions & 7 deletions packages/custom-elements-json-core/src/customElementsJson.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,31 @@
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;
}

setImportsForCurrentModule(imports: Import[]) {
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);
}
}
}

Expand Down
11 changes: 10 additions & 1 deletion packages/custom-elements-json-core/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>(C: T | Import): C is Import {
if ('importPath' in C && 'isBareModuleSpecifier' in C) {
return true;
}
return false;
}

1 change: 1 addition & 0 deletions packages/custom-elements-json-helpers/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,4 @@ export {
isField,
isMethod,
};

1 change: 1 addition & 0 deletions packages/custom-elements-json-helpers/src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
}
Expand Down
1 change: 1 addition & 0 deletions packages/custom-elements-json/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ export type Declaration =
| FunctionDeclaration
| MixinDeclaration
| VariableDeclaration
| MixinDeclaration
| CustomElement;

/**
Expand Down