From f373c5dc7134c65d8d0ffab14d7dd505e7f5c0f6 Mon Sep 17 00:00:00 2001 From: thepassle Date: Sun, 4 Jul 2021 11:19:45 +0200 Subject: [PATCH 1/3] feat: add support for interfaces --- packages/analyzer/custom-elements.json | 73 +++++++++++++------ .../analyzer/fixtures/-default/package/bar.js | 29 +++++--- .../creators/createFunctionLike.js | 1 + .../src/features/analyse-phase/exports.js | 15 ++++ .../src/features/analyse-phase/interfaces.js | 70 ++++++++++++++++++ packages/analyzer/src/features/index.js | 2 + 6 files changed, 158 insertions(+), 32 deletions(-) create mode 100644 packages/analyzer/src/features/analyse-phase/interfaces.js diff --git a/packages/analyzer/custom-elements.json b/packages/analyzer/custom-elements.json index 890170f1..71882fcb 100644 --- a/packages/analyzer/custom-elements.json +++ b/packages/analyzer/custom-elements.json @@ -7,45 +7,74 @@ "path": "fixtures/-default/package/bar.js", "declarations": [ { - "kind": "class", - "description": "", - "name": "MyEl", + "kind": "interface", + "name": "MyInterface", + "supertypes": [ + { + "name": "Foo", + "module": "fixtures/-default/package/bar.js" + }, + { + "name": "Bar", + "package": "bar" + }, + { + "name": "Baz", + "module": "fixtures/-default/package/bar.js" + } + ], "members": [ { "kind": "field", - "name": "bar", - "attribute": "bar" + "name": "name", + "type": { + "text": "string" + }, + "description": "the name of the class", + "summary": "this is the summary" }, { "kind": "field", - "name": "foo", - "privacy": "public", - "attribute": "foo" - } - ], - "attributes": [ + "name": "superClasses", + "type": { + "text": "SuperClass[]" + }, + "description": "super classes and mixins" + }, { - "name": "bar", - "fieldName": "bar" + "kind": "field", + "name": "member", + "type": { + "text": "ClassMember" + } }, { - "name": "foo", - "fieldName": "foo" + "kind": "method", + "name": "baz", + "return": { + "type": { + "text": "void" + } + }, + "parameters": [ + { + "name": "a", + "type": { + "text": "string" + } + } + ] } ], - "superclass": { - "name": "LitElement", - "module": "fixtures/-default/package/bar.js" - }, - "customElement": true + "description": "Description of the interface" } ], "exports": [ { "kind": "js", - "name": "MyEl", + "name": "MyInterface", "declaration": { - "name": "MyEl", + "name": "MyInterface", "module": "fixtures/-default/package/bar.js" } } diff --git a/packages/analyzer/fixtures/-default/package/bar.js b/packages/analyzer/fixtures/-default/package/bar.js index 573f8759..aeb38a9c 100644 --- a/packages/analyzer/fixtures/-default/package/bar.js +++ b/packages/analyzer/fixtures/-default/package/bar.js @@ -1,10 +1,19 @@ -export class MyEl extends LitElement { - static get properties() { - return { - foo: {type: String} - } - } - - @property() - bar; -} \ No newline at end of file +import type Bar from 'bar'; + +/** + * Description of the interface + */ +export interface MyInterface extends Foo, Bar implements Baz { + /** + * the name of the class + * @summary this is the summary + */ + name: string; + + /** super classes and mixins */ + superClasses: SuperClass[]; + + member: ClassMember; + + baz(a: string): void +} diff --git a/packages/analyzer/src/features/analyse-phase/creators/createFunctionLike.js b/packages/analyzer/src/features/analyse-phase/creators/createFunctionLike.js index 32f121e4..b7b229ff 100644 --- a/packages/analyzer/src/features/analyse-phase/creators/createFunctionLike.js +++ b/packages/analyzer/src/features/analyse-phase/creators/createFunctionLike.js @@ -28,6 +28,7 @@ export function handleKind(functionLike, node) { functionLike.kind = 'function'; break; case ts.SyntaxKind.MethodDeclaration: + case ts.SyntaxKind.MethodSignature: functionLike.kind = 'method'; break; } diff --git a/packages/analyzer/src/features/analyse-phase/exports.js b/packages/analyzer/src/features/analyse-phase/exports.js index 16a9e626..3d5a974d 100644 --- a/packages/analyzer/src/features/analyse-phase/exports.js +++ b/packages/analyzer/src/features/analyse-phase/exports.js @@ -33,6 +33,21 @@ export function exportsPlugin() { }); } + /** + * @example export interface Foo {} + */ + if(node.kind === ts.SyntaxKind.InterfaceDeclaration) { + const _export = { + kind: 'js', + name: node?.name?.getText() || '', + declaration: { + name: node?.name?.getText() || '', + module: moduleDoc.path, + } + } + moduleDoc.exports = [...(moduleDoc.exports || []), _export]; + } + /** * @example export default var1; */ diff --git a/packages/analyzer/src/features/analyse-phase/interfaces.js b/packages/analyzer/src/features/analyse-phase/interfaces.js new file mode 100644 index 00000000..1c8df972 --- /dev/null +++ b/packages/analyzer/src/features/analyse-phase/interfaces.js @@ -0,0 +1,70 @@ +import ts from 'typescript'; +import { has, resolveModuleOrPackageSpecifier } from '../../utils/index.js'; +import { createField } from './creators/createClassField.js'; +import { createFunctionLike } from './creators/createFunctionLike.js'; +import { handleJsDoc } from './creators/handlers.js'; + +/** + * interfacesPlugin + * + * handles interfaces + */ +export function interfacesPlugin() { + return { + name: 'CORE - INTERFACES', + analyzePhase({ts, node, moduleDoc, context}){ + switch(node.kind) { + case ts.SyntaxKind.InterfaceDeclaration: + const int = createInterface(node, moduleDoc, context); + moduleDoc.declarations.push(int); + break; + } + } + } +} + +function createInterface(node, moduleDoc, context) { + let int = { + kind: 'interface', + name: node?.name?.getText?.() || '', + supertypes: [], + members: [] + } + + /** Add description */ + int = handleJsDoc(int, node); + + /** Heritage */ + node?.heritageClauses?.forEach(clause => { + clause?.types?.forEach(type => { + const ref = { + name: type?.getText?.() || '', + ...resolveModuleOrPackageSpecifier(moduleDoc, context, type?.getText?.()) + } + int.supertypes.push(ref); + }); + }); + + /** Members */ + node?.members?.forEach(member => { + /** Properties */ + if(member.kind === ts.SyntaxKind.PropertySignature) { + const field = createField(member); + int.members.push(field); + } + + /** Methods */ + if(member.kind === ts.SyntaxKind.MethodSignature) { + const method = createFunctionLike(member); + int.members.push(method); + } + }); + + ['supertypes', 'members'].forEach(kind => { + if(!has(int[kind])) { + delete int[kind] + } + }); + + return int; +} \ No newline at end of file diff --git a/packages/analyzer/src/features/index.js b/packages/analyzer/src/features/index.js index c59ca346..afbc9fd4 100644 --- a/packages/analyzer/src/features/index.js +++ b/packages/analyzer/src/features/index.js @@ -6,6 +6,7 @@ import { collectImportsPlugin } from './collect-phase/collect-imports.js'; /** * ANALYSE */ +import { interfacesPlugin } from './analyse-phase/interfaces.js'; import { exportsPlugin } from './analyse-phase/exports.js'; import { customElementsDefineCallsPlugin } from './analyse-phase/custom-elements-define-calls.js'; import { functionLikePlugin } from './analyse-phase/function-like.js'; @@ -48,6 +49,7 @@ export const FEATURES = [ collectImportsPlugin(), /** ANALYSE */ + interfacesPlugin(), exportsPlugin(), customElementsDefineCallsPlugin(), functionLikePlugin(), From 7a3a3e94d7f293eeb59bc3ee098a48c1bb585e2c Mon Sep 17 00:00:00 2001 From: thepassle Date: Sun, 4 Jul 2021 12:36:29 +0200 Subject: [PATCH 2/3] feat: interface inheritance --- packages/analyzer/custom-elements.json | 160 ++++++++++++++---- .../analyzer/fixtures/-default/package/b.js | 4 + .../analyzer/fixtures/-default/package/bar.js | 42 +++-- .../post-processing/apply-inheritance.js | 69 +++++++- .../analyzer/src/utils/manifest-helpers.js | 14 ++ 5 files changed, 246 insertions(+), 43 deletions(-) create mode 100644 packages/analyzer/fixtures/-default/package/b.js diff --git a/packages/analyzer/custom-elements.json b/packages/analyzer/custom-elements.json index 71882fcb..4856d309 100644 --- a/packages/analyzer/custom-elements.json +++ b/packages/analyzer/custom-elements.json @@ -2,68 +2,154 @@ "schemaVersion": "1.0.0", "readme": "", "modules": [ + { + "kind": "javascript-module", + "path": "fixtures/-default/package/b.js", + "declarations": [ + { + "kind": "interface", + "name": "F", + "members": [ + { + "kind": "field", + "name": "f", + "type": { + "text": "string" + } + } + ] + } + ], + "exports": [ + { + "kind": "js", + "name": "F", + "declaration": { + "name": "F", + "module": "fixtures/-default/package/b.js" + } + } + ] + }, { "kind": "javascript-module", "path": "fixtures/-default/package/bar.js", "declarations": [ { "kind": "interface", - "name": "MyInterface", + "name": "G", + "members": [ + { + "kind": "field", + "name": "g", + "type": { + "text": "boolean" + } + } + ] + }, + { + "kind": "interface", + "name": "B", "supertypes": [ { - "name": "Foo", + "name": "F", + "package": "b.js" + }, + { + "name": "G", "module": "fixtures/-default/package/bar.js" + } + ], + "members": [ + { + "kind": "field", + "name": "b", + "type": { + "text": "string" + } }, { - "name": "Bar", + "kind": "field", + "name": "f", + "type": { + "text": "string" + }, + "inheritedFrom": { + "name": "F", + "module": "fixtures/-default/package/bar.js" + } + }, + { + "kind": "field", + "name": "g", + "type": { + "text": "boolean" + }, + "inheritedFrom": { + "name": "G", + "module": "fixtures/-default/package/bar.js" + } + } + ] + }, + { + "kind": "interface", + "name": "A", + "supertypes": [ + { + "name": "B", + "module": "fixtures/-default/package/bar.js" + }, + { + "name": "C", "package": "bar" }, { - "name": "Baz", + "name": "D", "module": "fixtures/-default/package/bar.js" } ], "members": [ { "kind": "field", - "name": "name", + "name": "a", "type": { "text": "string" - }, - "description": "the name of the class", - "summary": "this is the summary" + } }, { "kind": "field", - "name": "superClasses", + "name": "b", "type": { - "text": "SuperClass[]" + "text": "string" }, - "description": "super classes and mixins" + "inheritedFrom": { + "name": "B", + "module": "fixtures/-default/package/bar.js" + } }, { "kind": "field", - "name": "member", + "name": "f", "type": { - "text": "ClassMember" + "text": "string" + }, + "inheritedFrom": { + "name": "F", + "module": "fixtures/-default/package/bar.js" } }, { - "kind": "method", - "name": "baz", - "return": { - "type": { - "text": "void" - } + "kind": "field", + "name": "g", + "type": { + "text": "boolean" }, - "parameters": [ - { - "name": "a", - "type": { - "text": "string" - } - } - ] + "inheritedFrom": { + "name": "G", + "module": "fixtures/-default/package/bar.js" + } } ], "description": "Description of the interface" @@ -72,9 +158,25 @@ "exports": [ { "kind": "js", - "name": "MyInterface", + "name": "G", + "declaration": { + "name": "G", + "module": "fixtures/-default/package/bar.js" + } + }, + { + "kind": "js", + "name": "B", + "declaration": { + "name": "B", + "module": "fixtures/-default/package/bar.js" + } + }, + { + "kind": "js", + "name": "A", "declaration": { - "name": "MyInterface", + "name": "A", "module": "fixtures/-default/package/bar.js" } } diff --git a/packages/analyzer/fixtures/-default/package/b.js b/packages/analyzer/fixtures/-default/package/b.js new file mode 100644 index 00000000..d92b8fbd --- /dev/null +++ b/packages/analyzer/fixtures/-default/package/b.js @@ -0,0 +1,4 @@ + +export interface F { + f: string +} \ No newline at end of file diff --git a/packages/analyzer/fixtures/-default/package/bar.js b/packages/analyzer/fixtures/-default/package/bar.js index aeb38a9c..884f2206 100644 --- a/packages/analyzer/fixtures/-default/package/bar.js +++ b/packages/analyzer/fixtures/-default/package/bar.js @@ -1,19 +1,37 @@ -import type Bar from 'bar'; +import type C from 'bar'; +import F from 'b.js'; + +export interface G { + g: boolean; +} +export interface B extends F, G { + b: string; +} /** * Description of the interface */ -export interface MyInterface extends Foo, Bar implements Baz { - /** - * the name of the class - * @summary this is the summary - */ - name: string; +export interface A extends B, C implements D { + a: string; +} - /** super classes and mixins */ - superClasses: SuperClass[]; - member: ClassMember; +// import type Bar from 'bar'; - baz(a: string): void -} +// /** +// * Description of the interface +// */ +// export interface MyInterface extends Foo, Bar implements Baz { +// /** +// * the name of the class +// * @summary this is the summary +// */ +// name: string; + +// /** super classes and mixins */ +// superClasses: SuperClass[]; + +// member: ClassMember; + +// baz(a: string): void +// } diff --git a/packages/analyzer/src/features/post-processing/apply-inheritance.js b/packages/analyzer/src/features/post-processing/apply-inheritance.js index ddc2dd01..45d5c3f6 100644 --- a/packages/analyzer/src/features/post-processing/apply-inheritance.js +++ b/packages/analyzer/src/features/post-processing/apply-inheritance.js @@ -1,5 +1,5 @@ -import { getAllDeclarationsOfKind, getModuleForClassLike, getModuleFromManifest, getInheritanceTree } from '../../utils/manifest-helpers.js'; -import { resolveModuleOrPackageSpecifier } from '../../utils/index.js'; +import { getAllDeclarationsOfKind, getModuleForClassLike, getModuleFromManifest, getInheritanceTree, getModuleForInterface } from '../../utils/manifest-helpers.js'; +import { has, resolveModuleOrPackageSpecifier } from '../../utils/index.js'; /** * APPLY-INHERITANCE-PLUGIN @@ -12,6 +12,7 @@ export function applyInheritancePlugin() { packageLinkPhase({customElementsManifest, context}){ const classes = getAllDeclarationsOfKind(customElementsManifest, 'class'); const mixins = getAllDeclarationsOfKind(customElementsManifest, 'mixin'); + const interfaces = getAllDeclarationsOfKind(customElementsManifest, 'interface'); [...classes, ...mixins].forEach((customElement) => { const inheritanceChain = getInheritanceTree(customElementsManifest, customElement.name); @@ -61,6 +62,70 @@ export function applyInheritancePlugin() { }); }); }); + + interfaces?.forEach(int => { + const tree = getInterfaceInheritanceChain(customElementsManifest, int.name) + tree.forEach(supertype => { + /** Ignore the current interface itself */ + if(int.name === supertype.name) return; + + supertype?.members?.forEach(member => { + const containingModulePath = getModuleForInterface(customElementsManifest, int.name); + const containingModule = getModuleFromManifest(customElementsManifest, containingModulePath); + + const newItem = {...member}; + const existing = int?.members?.find(item => newItem.name === item.name); + + if (existing) { + existing.inheritedFrom = { + name: supertype.name, + ...resolveModuleOrPackageSpecifier(containingModule, context, supertype.name) + } + } else { + newItem.inheritedFrom = { + name: supertype.name, + ...resolveModuleOrPackageSpecifier(containingModule, context, supertype.name) + } + + int.members = [...(int.members || []), newItem]; + } + }); + }); + }); } } } + + +function getInterfaceInheritanceChain(customElementsManifest, name) { + const tree = []; + const interfacesMap = new Map(); + const interfaces = getAllDeclarationsOfKind(customElementsManifest, 'interface'); + interfaces.forEach(int => { + interfacesMap.set(int.name, int); + }); + + let currentInterface = interfacesMap.get(name); + + if(currentInterface) { + tree.push(currentInterface); + + currentInterface?.supertypes?.forEach(supertype => { + let foundSupertype = interfacesMap.get(supertype.name); + + if(foundSupertype) { + tree.push(foundSupertype) + while(has(foundSupertype?.supertypes)) { + foundSupertype.supertypes.forEach(supertype => { + foundSupertype = interfacesMap.get(supertype.name); + if(foundSupertype) { + tree.push(foundSupertype); + } + }) + } + } + }); + } + + return tree; +} \ No newline at end of file diff --git a/packages/analyzer/src/utils/manifest-helpers.js b/packages/analyzer/src/utils/manifest-helpers.js index 3a657b5d..5fe05905 100644 --- a/packages/analyzer/src/utils/manifest-helpers.js +++ b/packages/analyzer/src/utils/manifest-helpers.js @@ -140,6 +140,20 @@ export function getModuleForClassLike(cem, className) { return result; } +export function getModuleForInterface(cem, intName) { + let result = undefined; + + cem?.modules?.forEach(_module => { + _module?.declarations?.forEach(declaration => { + if((declaration.kind === 'interface') && declaration.name === intName) { + result = _module.path; + } + }); + }); + + return result; +} + /** * Given a manifest module, a class name, and a class member name, gets the * manifest doc for the module's class' member. From e5ed68b71c6c690035dbe1040ce0e20d30bf877d Mon Sep 17 00:00:00 2001 From: thepassle Date: Sun, 4 Jul 2021 12:59:48 +0200 Subject: [PATCH 3/3] fix: resolve correct supertype --- packages/analyzer/custom-elements.json | 4 ++-- .../src/features/post-processing/apply-inheritance.js | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/analyzer/custom-elements.json b/packages/analyzer/custom-elements.json index 4856d309..a27c9a86 100644 --- a/packages/analyzer/custom-elements.json +++ b/packages/analyzer/custom-elements.json @@ -77,7 +77,7 @@ }, "inheritedFrom": { "name": "F", - "module": "fixtures/-default/package/bar.js" + "module": "fixtures/-default/package/b.js" } }, { @@ -137,7 +137,7 @@ }, "inheritedFrom": { "name": "F", - "module": "fixtures/-default/package/bar.js" + "module": "fixtures/-default/package/b.js" } }, { diff --git a/packages/analyzer/src/features/post-processing/apply-inheritance.js b/packages/analyzer/src/features/post-processing/apply-inheritance.js index 45d5c3f6..22a08635 100644 --- a/packages/analyzer/src/features/post-processing/apply-inheritance.js +++ b/packages/analyzer/src/features/post-processing/apply-inheritance.js @@ -70,7 +70,7 @@ export function applyInheritancePlugin() { if(int.name === supertype.name) return; supertype?.members?.forEach(member => { - const containingModulePath = getModuleForInterface(customElementsManifest, int.name); + const containingModulePath = getModuleForInterface(customElementsManifest, supertype.name); const containingModule = getModuleFromManifest(customElementsManifest, containingModulePath); const newItem = {...member}; @@ -86,7 +86,6 @@ export function applyInheritancePlugin() { name: supertype.name, ...resolveModuleOrPackageSpecifier(containingModule, context, supertype.name) } - int.members = [...(int.members || []), newItem]; } });