diff --git a/packages/analyzer/custom-elements.json b/packages/analyzer/custom-elements.json index 890170f1..a27c9a86 100644 --- a/packages/analyzer/custom-elements.json +++ b/packages/analyzer/custom-elements.json @@ -2,50 +2,181 @@ "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": "class", - "description": "", - "name": "MyEl", + "kind": "interface", + "name": "G", + "members": [ + { + "kind": "field", + "name": "g", + "type": { + "text": "boolean" + } + } + ] + }, + { + "kind": "interface", + "name": "B", + "supertypes": [ + { + "name": "F", + "package": "b.js" + }, + { + "name": "G", + "module": "fixtures/-default/package/bar.js" + } + ], "members": [ { "kind": "field", - "name": "bar", - "attribute": "bar" + "name": "b", + "type": { + "text": "string" + } }, { "kind": "field", - "name": "foo", - "privacy": "public", - "attribute": "foo" + "name": "f", + "type": { + "text": "string" + }, + "inheritedFrom": { + "name": "F", + "module": "fixtures/-default/package/b.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": "D", + "module": "fixtures/-default/package/bar.js" } ], - "attributes": [ + "members": [ + { + "kind": "field", + "name": "a", + "type": { + "text": "string" + } + }, + { + "kind": "field", + "name": "b", + "type": { + "text": "string" + }, + "inheritedFrom": { + "name": "B", + "module": "fixtures/-default/package/bar.js" + } + }, { - "name": "bar", - "fieldName": "bar" + "kind": "field", + "name": "f", + "type": { + "text": "string" + }, + "inheritedFrom": { + "name": "F", + "module": "fixtures/-default/package/b.js" + } }, { - "name": "foo", - "fieldName": "foo" + "kind": "field", + "name": "g", + "type": { + "text": "boolean" + }, + "inheritedFrom": { + "name": "G", + "module": "fixtures/-default/package/bar.js" + } } ], - "superclass": { - "name": "LitElement", - "module": "fixtures/-default/package/bar.js" - }, - "customElement": true + "description": "Description of the interface" } ], "exports": [ { "kind": "js", - "name": "MyEl", + "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": "MyEl", + "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 573f8759..884f2206 100644 --- a/packages/analyzer/fixtures/-default/package/bar.js +++ b/packages/analyzer/fixtures/-default/package/bar.js @@ -1,10 +1,37 @@ -export class MyEl extends LitElement { - static get properties() { - return { - foo: {type: String} - } - } - - @property() - bar; -} \ No newline at end of file +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 A extends B, C implements D { + a: string; +} + + +// 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(), diff --git a/packages/analyzer/src/features/post-processing/apply-inheritance.js b/packages/analyzer/src/features/post-processing/apply-inheritance.js index ddc2dd01..22a08635 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,69 @@ 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, supertype.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.