From b1ca5cba1ad954d56f9247cb798c6fca371b8c7d Mon Sep 17 00:00:00 2001 From: 43081j <43081j@users.noreply.github.com> Date: Wed, 2 Jun 2021 22:43:25 +0100 Subject: [PATCH 01/13] latest manifest --- src/cli/cli.ts | 2 +- .../custom-elements-manifest/schema.ts | 585 ++++++++++++++++++ .../custom-elements-manifest/transformer.ts | 510 +++++++++++++++ src/transformers/transform-analyzer-result.ts | 4 +- src/transformers/transformer-kind.ts | 2 +- 5 files changed, 1100 insertions(+), 3 deletions(-) create mode 100644 src/transformers/custom-elements-manifest/schema.ts create mode 100644 src/transformers/custom-elements-manifest/transformer.ts diff --git a/src/cli/cli.ts b/src/cli/cli.ts index 46635e4f..2a77fe3f 100644 --- a/src/cli/cli.ts +++ b/src/cli/cli.ts @@ -50,7 +50,7 @@ o {tagname}: The element's tag name`, }) .option("format", { describe: `Specify output format`, - choices: ["md", "markdown", "json", "json2", "vscode"], + choices: ["md", "markdown", "json", "json2", "vscode", "custom-elements-manifest"], nargs: 1, alias: "f" }) diff --git a/src/transformers/custom-elements-manifest/schema.ts b/src/transformers/custom-elements-manifest/schema.ts new file mode 100644 index 00000000..d9891f6c --- /dev/null +++ b/src/transformers/custom-elements-manifest/schema.ts @@ -0,0 +1,585 @@ +/** + * This file comes from the following PR with a proposed JSON schema: + * https://github.com/webcomponents/custom-elements-manifest + */ + +/** + * @license + * Copyright (c) 2019 The Polymer Project Authors. All rights reserved. + * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt + * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt + * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt + * Code distributed by Google as part of the polymer project is also + * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt + */ + +/** + * The top-level interface of a custom elements manifest file. + * + * Because custom elements are JavaScript classes, describing a custom element + * may require describing arbitrary JavaScript concepts like modules, classes, + * functions, etc. So custom elements manifests are capable of documenting + * the elements in a package, as well as those JavaScript concepts. + * + * The modules described in a package should be the public entrypoints that + * other packages may import from. Multiple modules may export the same object + * via re-exports, but in most cases a package should document the single + * canonical export that should be used. + */ +export interface Package { + /** + * The version of the schema used in this file. + */ + schemaVersion: string; + + /** + * The Markdown to use for the main readme of this package. + * + * This can be used to override the readme used by Github or npm if that + * file contains information irrelevant to custom element catalogs and + * documentation viewers. + */ + readme?: string; + + /** + * An array of the modules this package contains. + */ + modules: Array; +} + +// This type may expand in the future to include JSON, CSS, or HTML +// modules. +export type Module = JavaScriptModule; + +export interface JavaScriptModule { + kind: "javascript-module"; + + path: string; + + /** + * A markdown summary suitable for display in a listing. + */ + summary?: string; + + /** + * A markdown description of the module. + */ + description?: string; + + /** + * The declarations of a module. + * + * For documentation purposes, all declarations that are reachable from + * exports should be described here. Ie, functions and objects that may be + * properties of exported objects, or passed as arguments to functions. + */ + declarations?: Array; + + /** + * The exports of a module. This includes JavaScript exports and + * custom element definitions. + */ + exports?: Array; +} + +export type Export = JavaScriptExport | CustomElementExport; + +export interface JavaScriptExport { + kind: "js"; + + /** + * The name of the exported symbol. + * + * JavaScript has a number of ways to export objects which determine the + * correct name to use. + * + * - Default exports must use the name "default". + * - Named exports use the name that is exported. If the export is renamed + * with the "as" clause, use the exported name. + * - Aggregating exports (`* from`) should use the name `*` + */ + name: string; + + /** + * A reference to the exported declaration. + * + * In the case of aggregating exports, the reference's `module` field must be + * defined and the `name` field must be `"*"`. + */ + declaration: Reference; +} + +/** + * A global custom element defintion, ie the result of a + * `customElements.define()` call. + * + * This is represented as an export because a definition makes the element + * available outside of the module it's defined it. + */ +export interface CustomElementExport { + kind: "custom-element-definition"; + + /** + * The tag name of the custom element. + */ + name: string; + + /** + * A reference to the class or other declaration that implements the + * custom element. + */ + declaration: Reference; +} + +export type Declaration = ClassDeclaration | FunctionDeclaration | MixinDeclaration | VariableDeclaration | CustomElementDeclaration; + +/** + * A reference to an export of a module. + * + * All references are required to be publically accessible, so the canonical + * representation of a reference is the export it's available from. + * + * `package` should generally refer to an npm package name. If `package` is + * undefined then the reference is local to this package. If `module` is + * undefined the reference is local to the containing module. + * + * References to global symbols like `Array`, `HTMLElement`, or `Event` should + * use a `package` name of `"global:"`. + */ +export interface Reference { + name: string; + package?: string; + module?: string; +} + +/** + * A reference to the source of a declaration or member. + */ +export interface SourceReference { + /** + * An absolute URL to the source (ie. a GitHub URL). + */ + href: string; +} + +/** + * A description of a custom element class. + * + * Custom elements are JavaScript classes, so this extends from + * `ClassDeclaration` and adds custom-element-specific features like + * attributes, events, and slots. + * + * Note that `tagName` in this interface is optional. Tag names are not + * neccessarily part of a custom element class, but belong to the definition + * (often called the "registration") or the `customElements.define()` call. + * + * Because classes and tag anmes can only be registered once, there's a + * one-to-one relationship between classes and tag names. For ease of use, + * we allow the tag name here. + * + * Some packages define and register custom elements in separate modules. In + * these cases one `Module` should contain the `CustomElement` without a + * tagName, and another `Module` should contain the + * `CustomElement`. + */ +export interface CustomElementDeclaration extends ClassDeclaration {} + +/** + * The additional fields that a custom element adds to classes and mixins. + */ +export interface CustomElement extends ClassLike { + /** + * An optional tag name that should be specified if this is a + * self-registering element. + * + * Self-registering elements must also include a CustomElementExport + * in the module's exports. + */ + tagName?: string; + + /** + * The attributes that this element is known to understand. + */ + attributes?: Attribute[]; + + /** + * The events that this element fires. + */ + events?: Event[]; + + /** + * The shadow dom content slots that this element accepts. + */ + slots?: Slot[]; + + cssParts?: CssPart[]; + + cssProperties?: CssCustomProperty[]; + + demos?: Demo[]; +} + +export interface Attribute { + name: string; + + /** + * A markdown summary suitable for display in a listing. + */ + summary?: string; + + /** + * A markdown description. + */ + description?: string; + + inheritedFrom?: Reference; + + /** + * The type that the attribute will be serialized/deserialized as. + */ + type?: Type; + + /** + * The default value of the attribute, if any. + * + * As attributes are always strings, this is the actual value, not a human + * readable description. + */ + default?: string; + + /** + * The name of the field this attribute is associated with, if any. + */ + fieldName?: string; +} + +export interface Event { + name: string; + + /** + * A markdown summary suitable for display in a listing. + */ + summary?: string; + + /** + * A markdown description. + */ + description?: string; + + /** + * The type of the event object that's fired. + */ + type: Type; + + inheritedFrom?: Reference; +} + +export interface Slot { + /** + * The slot name, or the empty string for an unnamed slot. + */ + name: string; + + /** + * A markdown summary suitable for display in a listing. + */ + summary?: string; + + /** + * A markdown description. + */ + description?: string; +} + +/** + * The description of a CSS Part + */ +export interface CssPart { + name: string; + + /** + * A markdown summary suitable for display in a listing. + */ + summary?: string; + + /** + * A markdown description. + */ + description?: string; +} + +export interface CssCustomProperty { + /** + * The name of the property, including leading `--`. + */ + name: string; + + default?: string; + + /** + * A markdown summary suitable for display in a listing. + */ + summary?: string; + + /** + * A markdown description. + */ + description?: string; +} + +export interface Type { + /** + * The full string representation of the type, in whatever type syntax is + * used, such as JSDoc, Closure, or TypeScript. + */ + text: string; + + /** + * An array of references to the types in the type string. + * + * These references have optional indices into the type string so that tools + * can understand the references in the type string independently of the type + * system and syntax. For example, a documentation viewer could display the + * type `Array` with cross-references to `FooElement` + * and `BarElement` without understanding arrays, generics, or union types. + */ + references?: TypeReference[]; + + source?: SourceReference; +} + +/** + * A reference that is associated with a type string and optionally a range + * within the string. + * + * Start and end must both be present or not present. If they're present, they + * are indices into the associated type string. If they are missing, the entire + * type string is the symbol referenced and the name should match the type + * string. + */ +export interface TypeReference extends Reference { + start?: number; + end?: number; +} + +/** + * The common interface of classes and mixins. + */ +export interface ClassLike { + name: string; + + /** + * A markdown summary suitable for display in a listing. + */ + summary?: string; + + /** + * A markdown description of the class. + */ + description?: string; + + /** + * The superclass of this class. + * + * If this class is defined with mixin + * applications, the prototype chain includes the mixin applications + * and the true superclass is computed from them. + */ + superclass?: Reference; + + /** + * Any class mixins applied in the extends clause of this class. + * + * If mixins are applied in the class definition, then the true superclass + * of this class is the result of applying mixins in order to the superclass. + * + * Mixins must be listed in order of their application to the superclass or + * previous mixin application. This means that the innermost mixin is listed + * first. This may read backwards from the common order in JavaScript, but + * matches the order of language used to describe mixin application, like + * "S with A, B". + * + * @example + * + * ```javascript + * class T extends B(A(S)) {} + * ``` + * + * is described by: + * ```json + * { + * "kind": "class", + * "superclass": { + * "name": "S" + * }, + * "mixins": [ + * { + * "name": "A" + * }, + * { + * "name": "B" + * }, + * ] + * } + * ``` + */ + mixins?: Array; + members?: Array; + + source?: SourceReference; +} + +export interface ClassDeclaration extends ClassLike { + kind: "class"; +} + +export type ClassMember = ClassField | ClassMethod; + +/** + * The common interface of variables, class fields, and function + * parameters. + */ +export interface PropertyLike { + name: string; + + /** + * A markdown summary suitable for display in a listing. + */ + summary?: string; + + /** + * A markdown description of the field. + */ + description?: string; + + type?: Type; + + default?: string; +} + +export interface ClassField extends PropertyLike { + kind: "field"; + static?: boolean; + privacy?: Privacy; + inheritedFrom?: Reference; + source?: SourceReference; +} + +export interface ClassMethod extends FunctionLike { + kind: "method"; + static?: boolean; + privacy?: Privacy; + inheritedFrom?: Reference; + source?: SourceReference; +} + +/** + * A description of a class mixin. + * + * Mixins are functions which generate a new subclass of a given superclass. + * This interfaces describes the class and custom element features that + * are added by the mixin. As such, it extends the CustomElement interface and + * ClassLike interface. + * + * Since mixins are functions, it also extends the FunctionLike interface. This + * means a mixin is callable, and has parameters and a return type. + * + * The return type is often hard or impossible to accurately describe in type + * systems like TypeScript. It requires generics and an `extends` operator + * that TypeScript lacks. Therefore it's recommended that the return type is + * left empty. The most common form of a mixin function takes a single + * argument, so consumers of this interface should assume that the return type + * is the single argument subclassed by this declaration. + * + * A mixin should not have a superclass. If a mixins composes other mixins, + * they should be listed in the `mixins` field. + * + * See [this article]{@link https://justinfagnani.com/2015/12/21/real-mixins-with-javascript-classes/} + * for more information on the classmixin pattern in JavaScript. + * + * @example + * + * This JavaScript mixin declaration: + * ```javascript + * const MyMixin = (base) => class extends base { + * foo() { ... } + * } + * ``` + * + * Is described by this JSON: + * ```json + * { + * "kind": "mixin", + * "name": "MyMixin", + * "parameters": [ + * { + * "name": "base", + * } + * ], + * "members": [ + * { + * "kind": "method", + * "name": "foo", + * } + * ] + * } + * ``` + */ +export interface MixinDeclaration extends CustomElement, FunctionLike { + kind: "mixin"; +} + +export interface VariableDeclaration extends PropertyLike { + kind: "variable"; + source?: SourceReference; +} + +export interface FunctionDeclaration extends FunctionLike { + kind: "function"; + source?: SourceReference; +} + +export interface Parameter extends PropertyLike { + /** + * Whether the parameter is optional. Undefined implies non-optional. + */ + optional?: boolean; +} + +export interface FunctionLike { + name: string; + + /** + * A markdown summary suitable for display in a listing. + */ + summary?: string; + + /** + * A markdown description. + */ + description?: string; + + parameters?: Parameter[]; + + return?: { + type?: Type; + description?: string; + }; +} + +export type Privacy = "public" | "private" | "protected"; + +export interface Demo { + /** + * A markdown description of the demo. + */ + description?: string; + + /** + * Relative URL of the demo if it's published with the package. Absolute URL + * if it's hosted. + */ + url: string; + + source?: SourceReference; +} diff --git a/src/transformers/custom-elements-manifest/transformer.ts b/src/transformers/custom-elements-manifest/transformer.ts new file mode 100644 index 00000000..d589e42c --- /dev/null +++ b/src/transformers/custom-elements-manifest/transformer.ts @@ -0,0 +1,510 @@ +import { basename, relative } from "path"; +import * as tsModule from "typescript"; +import { SimpleType } from "ts-simple-type"; +import { Node, Program, SourceFile, Type, TypeChecker } from "typescript"; +import { AnalyzerResult } from "../../analyze/types/analyzer-result"; +import { ComponentDeclaration, ComponentHeritageClause } from "../../analyze/types/component-declaration"; +import { ComponentFeatureBase } from "../../analyze/types/features/component-feature"; +import { JsDoc } from "../../analyze/types/js-doc"; +import { findParent, getNodeName, resolveDeclarations } from "../../analyze/util/ast-util"; +import { getMixinHeritageClauses, getSuperclassHeritageClause, visitAllHeritageClauses } from "../../analyze/util/component-declaration-util"; +import { getJsDoc } from "../../analyze/util/js-doc-util"; +import { arrayDefined } from "../../util/array-util"; +import { getTypeHintFromType } from "../../util/get-type-hint-from-type"; +import { filterVisibility } from "../../util/model-util"; +import { TransformerConfig } from "../transformer-config"; +import { TransformerFunction } from "../transformer-function"; +import * as schema from "./schema"; + +interface TransformerContext { + config: TransformerConfig; + checker: TypeChecker; + program: Program; + ts: typeof tsModule; +} + +/** + * Transforms results to a custom elements manifest + * @param results + * @param program + * @param config + */ +export const transformer: TransformerFunction = (results: AnalyzerResult[], program: Program, config: TransformerConfig): string => { + const context: TransformerContext = { + config, + checker: program.getTypeChecker(), + program, + ts: tsModule + }; + + // Flatten analyzer results expanding inherited declarations into the declaration array. + const flattenedAnalyzerResults = flattenAnalyzerResults(results); + + // Transform all analyzer results into modules + const modules = flattenedAnalyzerResults.map(result => resultToModule(result, context)); + + const manifest: schema.Package = { + schemaVersion: "experimental", + modules + }; + + return JSON.stringify(manifest, null, 2); +}; + +/** + * Transforms an analyzer result into a module + * @param result + * @param context + */ +function resultToModule(result: AnalyzerResult, context: TransformerContext): schema.JavaScriptModule { + const exports = getExportsFromResult(result, context); + const declarations = getDeclarationsFromResult(result, context); + + return { + kind: "javascript-module", + path: getRelativePath(result.sourceFile.fileName, context), + declarations: declarations.length === 0 ? undefined : declarations, + exports: exports.length === 0 ? undefined : exports + }; +} + +/** + * Returns exports in an analyzer result + * @param result + * @param context + */ +function getExportsFromResult(result: AnalyzerResult, context: TransformerContext): schema.Export[] { + return [...getCustomElementExportsFromResult(result, context)]; +} + +/** + * Returns declarations in an analyzer result + * @param result + * @param context + */ +function getDeclarationsFromResult(result: AnalyzerResult, context: TransformerContext): schema.Declaration[] { + return [ + ...getClassesFromResult(result, context), + ...getFunctionsFromResult(result, context), + ...getVariablesFromResult(result, context) + // TODO (43081j): + // ...getCustomElementsFromResult(result, context) + ]; +} + +/** + * Returns functions in an analyzer result + * @param result + * @param context + */ +function getFunctionsFromResult(result: AnalyzerResult, context: TransformerContext): schema.FunctionDeclaration[] { + // TODO: support function exports + return []; +} + +function* getCustomElementExportsFromResult(result: AnalyzerResult, context: TransformerContext): IterableIterator { + for (const definition of result.componentDefinitions) { + // It's not possible right now to model a tag name where the + // declaration couldn't be resolved because the "declaration" is required + if (definition.declaration == null) { + continue; + } + + yield { + kind: "custom-element-definition", + name: definition.tagName, + declaration: getReferenceForNode(definition.declaration.node, context) + }; + } +} + +/** + * Returns variables in an analyzer result + * @param result + * @param context + */ +function* getVariablesFromResult(result: AnalyzerResult, context: TransformerContext): IterableIterator { + // Get all export symbols in the source file + const symbol = context.checker.getSymbolAtLocation(result.sourceFile); + if (symbol == null) { + return; + } + + const exports = context.checker.getExportsOfModule(symbol); + + // Convert all export variables to VariableDocs + for (const exp of exports) { + switch (exp.flags) { + case tsModule.SymbolFlags.BlockScopedVariable: + case tsModule.SymbolFlags.Variable: { + const node = exp.valueDeclaration; + + if (tsModule.isVariableDeclaration(node)) { + // Get the nearest variable statement in order to read the jsdoc + const variableStatement = findParent(node, tsModule.isVariableStatement) || node; + const jsDoc = getJsDoc(variableStatement, tsModule); + + yield { + kind: "variable", + name: node.name.getText(), + description: jsDoc?.description, + type: typeToSchemaType(context, context.checker.getTypeAtLocation(node)), + summary: getSummaryFromJsDoc(jsDoc) + }; + } + break; + } + } + } +} + +/** + * Returns classes in an analyzer result + * @param result + * @param context + */ +function* getClassesFromResult(result: AnalyzerResult, context: TransformerContext): IterableIterator { + if (result.declarations) { + for (const decl of result.declarations) { + const doc = getDeclarationFromDeclaration(decl, result, context); + if (doc) { + yield doc; + } + } + } +} + +/** + * Converts a component declaration to schema declaration + * @param declaration + * @param result + * @param context + */ +function getDeclarationFromDeclaration( + declaration: ComponentDeclaration, + result: AnalyzerResult, + context: TransformerContext +): schema.Declaration | undefined { + if (declaration.kind === "interface") { + return undefined; + } + + // Get the superclass of this declaration + const superclassHeritage = getSuperclassHeritageClause(declaration); + const superclassRef = superclassHeritage === undefined ? undefined : getReferenceFromHeritageClause(superclassHeritage, context); + + // Get all mixins + const mixinHeritage = getMixinHeritageClauses(declaration); + const mixinRefs = arrayDefined(mixinHeritage.map(h => getReferenceFromHeritageClause(h, context))); + + const members = getClassMembersForDeclaration(declaration, context); + + const classDecl: schema.ClassDeclaration = { + kind: "class", + superclass: superclassRef, + mixins: mixinRefs.length > 0 ? mixinRefs : undefined, + description: declaration.jsDoc?.description, + name: declaration.symbol?.name || getNodeName(declaration.node, { ts: tsModule }) || "", + members: members.length > 0 ? members : undefined, + summary: getSummaryFromJsDoc(declaration.jsDoc) + }; + + return classDecl; +} + +/** + * Returns class member docs for a declaration + * @param declaration + * @param context + */ +function getClassMembersForDeclaration(declaration: ComponentDeclaration, context: TransformerContext): schema.ClassMember[] { + return [...getClassFieldsForDeclaration(declaration, context), ...getMethodsForDeclaration(declaration, context)]; +} + +/** + * Returns method docs for a declaration + * @param declaration + * @param context + */ +function* getMethodsForDeclaration(declaration: ComponentDeclaration, context: TransformerContext): IterableIterator { + for (const method of filterVisibility(context.config.visibility, declaration.methods)) { + const parameters: schema.Parameter[] = []; + const node = method.node; + let returnType: Type | undefined = undefined; + + if (node !== undefined && tsModule.isMethodDeclaration(node)) { + for (const param of node.parameters) { + const name = param.name.getText(); + const { description, typeHint } = getParameterFromJsDoc(name, method.jsDoc); + + parameters.push({ + name: name, + type: typeToSchemaType(context, typeHint || (param.type != null ? context.checker.getTypeAtLocation(param.type) : undefined)), + description: description, + optional: param.questionToken !== undefined + }); + } + + // Get return type + const signature = context.checker.getSignatureFromDeclaration(node); + if (signature != null) { + returnType = context.checker.getReturnTypeOfSignature(signature); + } + } + + // Get return info from jsdoc + const { description: returnDescription, typeHint: returnTypeHint } = getReturnFromJsDoc(method.jsDoc); + + yield { + kind: "method", + name: method.name, + privacy: method.visibility, + description: method.jsDoc?.description, + parameters, + return: { + description: returnDescription, + type: typeToSchemaType(context, returnTypeHint || returnType) + }, + inheritedFrom: getInheritedFromReference(declaration, method, context), + summary: getSummaryFromJsDoc(method.jsDoc) + // TODO: "static" + }; + } +} + +/** + * Returns fields from a declaration + * @param declaration + * @param context + */ +function* getClassFieldsForDeclaration(declaration: ComponentDeclaration, context: TransformerContext): IterableIterator { + for (const member of filterVisibility(context.config.visibility, declaration.members)) { + if (member.propName != null) { + yield { + kind: "field", + name: member.propName, + privacy: member.visibility, + description: member.jsDoc?.description, + type: typeToSchemaType(context, member.typeHint || member.type?.()), + default: member.default != null ? JSON.stringify(member.default) : undefined, + inheritedFrom: getInheritedFromReference(declaration, member, context), + summary: getSummaryFromJsDoc(member.jsDoc) + // TODO: "static" + }; + } + } +} + +function getInheritedFromReference( + onDeclaration: ComponentDeclaration, + feature: ComponentFeatureBase, + context: TransformerContext +): schema.Reference | undefined { + if (feature.declaration != null && feature.declaration !== onDeclaration) { + return getReferenceForNode(feature.declaration.node, context); + } + + return undefined; +} + +/** + * Returns a Reference to a node + * @param node + * @param context + */ +function getReferenceForNode(node: Node, context: TransformerContext): schema.Reference { + const sourceFile = node.getSourceFile(); + const name = getNodeName(node, context) as string; + + // Test if the source file is from a typescript lib + // TODO: Find a better way of checking this + const isLib = sourceFile.isDeclarationFile && sourceFile.fileName.match(/typescript\/lib.*\.d\.ts$/) != null; + if (isLib) { + // Only return the name of the declaration if it's from lib + return { + name + }; + } + + // Test if the source file is located in a package + const packageName = getPackageName(sourceFile); + if (packageName != null) { + return { + name, + package: packageName + }; + } + + // Get the module path name + const module = getRelativePath(sourceFile.fileName, context); + return { + name, + module + }; +} + +/** + * Returns the name of the package (if any) + * @param sourceFile + */ +function getPackageName(sourceFile: SourceFile): string | undefined { + // TODO: Make it possible to access the ModuleResolutionHost + // in order to resolve the package using "resolveModuleNames" + // The following approach is very, very naive and is only temporary. + const match = sourceFile.fileName.match(/node_modules\/(.*?)\//); + + if (match != null) { + return match[1]; + } + + return undefined; +} + +/** + * Returns a relative path based on "cwd" in the config + * @param fullPath + * @param context + */ +function getRelativePath(fullPath: string, context: TransformerContext) { + return context.config.cwd != null ? `./${relative(context.config.cwd, fullPath)}` : basename(fullPath); +} + +/** + * Returns description and typeHint based on jsdoc for a specific parameter name + * @param name + * @param jsDoc + */ +function getParameterFromJsDoc(name: string, jsDoc: JsDoc | undefined): { description?: string; typeHint?: string } { + if (jsDoc?.tags == undefined) { + return {}; + } + + for (const tag of jsDoc.tags) { + const parsed = tag.parsed(); + + if (parsed.tag === "param" && parsed.name === name) { + return { description: parsed.description, typeHint: parsed.type }; + } + } + + return {}; +} + +/** + * Get return description and return typeHint from jsdoc + * @param jsDoc + */ +function getReturnFromJsDoc(jsDoc: JsDoc | undefined): { description?: string; typeHint?: string } { + const tag = jsDoc?.tags?.find(tag => ["returns", "return"].includes(tag.tag)); + + if (tag == null) { + return {}; + } + + const parsed = tag.parsed(); + return { description: parsed.description, typeHint: parsed.type }; +} + +/** + * Converts a heritage clause into a reference + * @param heritage + * @param context + */ +function getReferenceFromHeritageClause(heritage: ComponentHeritageClause, context: TransformerContext): schema.Reference | undefined { + const node = heritage.declaration?.node; + const identifier = heritage.identifier; + + // Return a reference for this node if any + if (node != null) { + return getReferenceForNode(node, context); + } + + // Try to get declaration of the identifier if no node was found + const [declaration] = resolveDeclarations(identifier, context); + if (declaration != null) { + return getReferenceForNode(declaration, context); + } + + // Just return the name of the reference if nothing could be resolved + const name = getNodeName(identifier, context); + if (name != null) { + return { name }; + } + + return undefined; +} + +/** + * Flatten all analyzer results with inherited declarations + * @param results + */ +function flattenAnalyzerResults(results: AnalyzerResult[]): AnalyzerResult[] { + // Keep track of declarations in each source file + const declarationMap = new Map>(); + + /** + * Add a declaration to the declaration map + * @param declaration + */ + function addDeclarationToMap(declaration: ComponentDeclaration) { + const sourceFile = declaration.node.getSourceFile(); + + const exportDocs = declarationMap.get(sourceFile) || new Set(); + + if (!declarationMap.has(sourceFile)) { + declarationMap.set(sourceFile, exportDocs); + } + + exportDocs.add(declaration); + } + + for (const result of results) { + for (const decl of result.declarations || []) { + // Add all existing declarations to the map + addDeclarationToMap(decl); + + visitAllHeritageClauses(decl, clause => { + // Flatten all component declarations + if (clause.declaration != null) { + addDeclarationToMap(clause.declaration); + } + }); + } + } + + // Return new results with flattened declarations + return results.map(result => { + const declarations = declarationMap.get(result.sourceFile); + + return { + ...result, + declarations: declarations != null ? Array.from(declarations) : result.declarations + }; + }); +} + +/** + * Returns the content of the summary jsdoc tag if any + * @param jsDoc + */ +function getSummaryFromJsDoc(jsDoc: JsDoc | undefined): string | undefined { + const summaryTag = jsDoc?.tags?.find(tag => tag.tag === "summary"); + + if (summaryTag == null) { + return undefined; + } + + return summaryTag.comment; +} + +function typeToSchemaType(context: TransformerContext, type: string | Type | SimpleType | undefined): schema.Type | undefined { + const hint = getTypeHintFromType(type, context.checker, context.config); + + if (!hint) { + return undefined; + } + + return { + text: hint + }; +} diff --git a/src/transformers/transform-analyzer-result.ts b/src/transformers/transform-analyzer-result.ts index d2b06b9d..15ba3c73 100644 --- a/src/transformers/transform-analyzer-result.ts +++ b/src/transformers/transform-analyzer-result.ts @@ -3,6 +3,7 @@ import { AnalyzerResult } from "../analyze/types/analyzer-result"; import { debugJsonTransformer } from "./debug/debug-json-transformer"; import { jsonTransformer } from "./json/json-transformer"; import { json2Transformer } from "./json2/json2-transformer"; +import { transformer as customElementsTransformer } from "./custom-elements-manifest/transformer"; import { markdownTransformer } from "./markdown/markdown-transformer"; import { TransformerConfig } from "./transformer-config"; import { TransformerFunction } from "./transformer-function"; @@ -15,7 +16,8 @@ const transformerFunctionMap: Record = { json2: json2Transformer, markdown: markdownTransformer, md: markdownTransformer, - vscode: vscodeTransformer + vscode: vscodeTransformer, + customElementsManifest: customElementsTransformer }; /** diff --git a/src/transformers/transformer-kind.ts b/src/transformers/transformer-kind.ts index c87a2f3a..82f7da02 100644 --- a/src/transformers/transformer-kind.ts +++ b/src/transformers/transformer-kind.ts @@ -1 +1 @@ -export type TransformerKind = "md" | "markdown" | "json" | "vscode" | "debug" | "json2"; +export type TransformerKind = "md" | "markdown" | "json" | "vscode" | "debug" | "json2" | "customElementsManifest"; From cc28db9984f6cd650837c16857f46d94e3c5d6ff Mon Sep 17 00:00:00 2001 From: 43081j <43081j@users.noreply.github.com> Date: Fri, 4 Jun 2021 19:08:45 +0100 Subject: [PATCH 02/13] add function declarations --- src/cli/analyze/analyze-cli-command.ts | 3 +- .../custom-elements-manifest/transformer.ts | 59 ++++++++++++++----- src/transformers/transform-analyzer-result.ts | 2 +- src/transformers/transformer-kind.ts | 2 +- 4 files changed, 48 insertions(+), 18 deletions(-) diff --git a/src/cli/analyze/analyze-cli-command.ts b/src/cli/analyze/analyze-cli-command.ts index 419861a2..73eb8c72 100644 --- a/src/cli/analyze/analyze-cli-command.ts +++ b/src/cli/analyze/analyze-cli-command.ts @@ -28,7 +28,7 @@ export const analyzeCliCommand: CliCommand = async (config: AnalyzerCliConfig): ` !!!!!!!!!!!!! WARNING !!!!!!!!!!!!! The custom-elements.json format is for experimental purposes. You can expect changes to this format. -Please follow and contribute to the discussion at: +Please follow and contribute to the discussion at: - https://github.com/webcomponents/custom-elements-json - https://github.com/w3c/webcomponents/issues/776 !!!!!!!!!!!!! WARNING !!!!!!!!!!!!! @@ -42,6 +42,7 @@ Please follow and contribute to the discussion at: if (config.outDir == null && config.outFile == null && config.outFiles == null) { switch (config.format) { case "json2": + case "custom-elements-manifest": // "json2" will need to output everything at once return "console_bulk"; default: diff --git a/src/transformers/custom-elements-manifest/transformer.ts b/src/transformers/custom-elements-manifest/transformer.ts index d589e42c..8e358349 100644 --- a/src/transformers/custom-elements-manifest/transformer.ts +++ b/src/transformers/custom-elements-manifest/transformer.ts @@ -85,23 +85,12 @@ function getExportsFromResult(result: AnalyzerResult, context: TransformerContex function getDeclarationsFromResult(result: AnalyzerResult, context: TransformerContext): schema.Declaration[] { return [ ...getClassesFromResult(result, context), - ...getFunctionsFromResult(result, context), - ...getVariablesFromResult(result, context) + ...getExportedSymbolsFromResult(result, context) // TODO (43081j): // ...getCustomElementsFromResult(result, context) ]; } -/** - * Returns functions in an analyzer result - * @param result - * @param context - */ -function getFunctionsFromResult(result: AnalyzerResult, context: TransformerContext): schema.FunctionDeclaration[] { - // TODO: support function exports - return []; -} - function* getCustomElementExportsFromResult(result: AnalyzerResult, context: TransformerContext): IterableIterator { for (const definition of result.componentDefinitions) { // It's not possible right now to model a tag name where the @@ -123,7 +112,7 @@ function* getCustomElementExportsFromResult(result: AnalyzerResult, context: Tra * @param result * @param context */ -function* getVariablesFromResult(result: AnalyzerResult, context: TransformerContext): IterableIterator { +function* getExportedSymbolsFromResult(result: AnalyzerResult, context: TransformerContext): IterableIterator { // Get all export symbols in the source file const symbol = context.checker.getSymbolAtLocation(result.sourceFile); if (symbol == null) { @@ -134,11 +123,11 @@ function* getVariablesFromResult(result: AnalyzerResult, context: TransformerCon // Convert all export variables to VariableDocs for (const exp of exports) { + const node = exp.valueDeclaration; + switch (exp.flags) { case tsModule.SymbolFlags.BlockScopedVariable: case tsModule.SymbolFlags.Variable: { - const node = exp.valueDeclaration; - if (tsModule.isVariableDeclaration(node)) { // Get the nearest variable statement in order to read the jsdoc const variableStatement = findParent(node, tsModule.isVariableStatement) || node; @@ -154,6 +143,46 @@ function* getVariablesFromResult(result: AnalyzerResult, context: TransformerCon } break; } + case tsModule.SymbolFlags.Function: { + if (tsModule.isFunctionDeclaration(node) && node.name) { + const jsDoc = getJsDoc(node, tsModule); + const parameters: schema.Parameter[] = []; + let returnType: Type | undefined = undefined; + + for (const param of node.parameters) { + const name = param.name.getText(); + const { description, typeHint } = getParameterFromJsDoc(name, jsDoc); + const type = typeToSchemaType(context, typeHint || (param.type != null ? context.checker.getTypeAtLocation(param.type) : undefined)); + + parameters.push({ + name, + type, + description, + optional: param.questionToken !== undefined + }); + } + + const signature = context.checker.getSignatureFromDeclaration(node); + if (signature != null) { + returnType = context.checker.getReturnTypeOfSignature(signature); + } + + const { description: returnDescription, typeHint: returnTypeHint } = getReturnFromJsDoc(jsDoc); + + yield { + kind: "function", + name: node.name.getText(), + description: jsDoc?.description, + summary: getSummaryFromJsDoc(jsDoc), + parameters, + return: { + type: typeToSchemaType(context, returnTypeHint || returnType), + description: returnDescription + } + }; + } + break; + } } } } diff --git a/src/transformers/transform-analyzer-result.ts b/src/transformers/transform-analyzer-result.ts index 15ba3c73..38af7bc2 100644 --- a/src/transformers/transform-analyzer-result.ts +++ b/src/transformers/transform-analyzer-result.ts @@ -17,7 +17,7 @@ const transformerFunctionMap: Record = { markdown: markdownTransformer, md: markdownTransformer, vscode: vscodeTransformer, - customElementsManifest: customElementsTransformer + "custom-elements-manifest": customElementsTransformer }; /** diff --git a/src/transformers/transformer-kind.ts b/src/transformers/transformer-kind.ts index 82f7da02..4d88e2ed 100644 --- a/src/transformers/transformer-kind.ts +++ b/src/transformers/transformer-kind.ts @@ -1 +1 @@ -export type TransformerKind = "md" | "markdown" | "json" | "vscode" | "debug" | "json2" | "customElementsManifest"; +export type TransformerKind = "md" | "markdown" | "json" | "vscode" | "debug" | "json2" | "custom-elements-manifest"; From c0d4496f34be6570ec69dfc69a6c7e4da329b87a Mon Sep 17 00:00:00 2001 From: 43081j <43081j@users.noreply.github.com> Date: Fri, 4 Jun 2021 19:25:02 +0100 Subject: [PATCH 03/13] add js exports --- .../custom-elements-manifest/transformer.ts | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/transformers/custom-elements-manifest/transformer.ts b/src/transformers/custom-elements-manifest/transformer.ts index 8e358349..b425e740 100644 --- a/src/transformers/custom-elements-manifest/transformer.ts +++ b/src/transformers/custom-elements-manifest/transformer.ts @@ -74,7 +74,7 @@ function resultToModule(result: AnalyzerResult, context: TransformerContext): sc * @param context */ function getExportsFromResult(result: AnalyzerResult, context: TransformerContext): schema.Export[] { - return [...getCustomElementExportsFromResult(result, context)]; + return [...getCustomElementExportsFromResult(result, context), ...getExportedNamesFromResult(result, context)]; } /** @@ -85,7 +85,7 @@ function getExportsFromResult(result: AnalyzerResult, context: TransformerContex function getDeclarationsFromResult(result: AnalyzerResult, context: TransformerContext): schema.Declaration[] { return [ ...getClassesFromResult(result, context), - ...getExportedSymbolsFromResult(result, context) + ...getExportedDeclarationsFromResult(result, context) // TODO (43081j): // ...getCustomElementsFromResult(result, context) ]; @@ -107,12 +107,29 @@ function* getCustomElementExportsFromResult(result: AnalyzerResult, context: Tra } } +function* getExportedNamesFromResult(result: AnalyzerResult, context: TransformerContext): IterableIterator { + const symbol = context.checker.getSymbolAtLocation(result.sourceFile); + if (symbol == null) { + return; + } + + const exports = context.checker.getExportsOfModule(symbol); + + for (const exp of exports) { + yield { + kind: "js", + name: exp.name, + declaration: getReferenceForNode(exp.valueDeclaration, context) + }; + } +} + /** * Returns variables in an analyzer result * @param result * @param context */ -function* getExportedSymbolsFromResult(result: AnalyzerResult, context: TransformerContext): IterableIterator { +function* getExportedDeclarationsFromResult(result: AnalyzerResult, context: TransformerContext): IterableIterator { // Get all export symbols in the source file const symbol = context.checker.getSymbolAtLocation(result.sourceFile); if (symbol == null) { From 1e0b46598710e4296346979ea84e715be74aa396 Mon Sep 17 00:00:00 2001 From: 43081j <43081j@users.noreply.github.com> Date: Fri, 4 Jun 2021 20:10:44 +0100 Subject: [PATCH 04/13] add some class discovery --- .../custom-elements-manifest/transformer.ts | 113 ++++++++---------- 1 file changed, 52 insertions(+), 61 deletions(-) diff --git a/src/transformers/custom-elements-manifest/transformer.ts b/src/transformers/custom-elements-manifest/transformer.ts index b425e740..f2d75cd3 100644 --- a/src/transformers/custom-elements-manifest/transformer.ts +++ b/src/transformers/custom-elements-manifest/transformer.ts @@ -9,7 +9,6 @@ import { JsDoc } from "../../analyze/types/js-doc"; import { findParent, getNodeName, resolveDeclarations } from "../../analyze/util/ast-util"; import { getMixinHeritageClauses, getSuperclassHeritageClause, visitAllHeritageClauses } from "../../analyze/util/component-declaration-util"; import { getJsDoc } from "../../analyze/util/js-doc-util"; -import { arrayDefined } from "../../util/array-util"; import { getTypeHintFromType } from "../../util/get-type-hint-from-type"; import { filterVisibility } from "../../util/model-util"; import { TransformerConfig } from "../transformer-config"; @@ -83,12 +82,58 @@ function getExportsFromResult(result: AnalyzerResult, context: TransformerContex * @param context */ function getDeclarationsFromResult(result: AnalyzerResult, context: TransformerContext): schema.Declaration[] { - return [ - ...getClassesFromResult(result, context), - ...getExportedDeclarationsFromResult(result, context) - // TODO (43081j): - // ...getCustomElementsFromResult(result, context) - ]; + return [...getExportedDeclarationsFromResult(result, context), ...getDeclarationsForResult(result, context)]; +} + +function* getDeclarationsForResult(result: AnalyzerResult, context: TransformerContext): IterableIterator { + if (result.declarations) { + for (const decl of result.declarations) { + const schemaDecl = getDeclarationForComponentDeclaration(decl, result, context); + if (schemaDecl) { + yield schemaDecl; + } + } + } +} + +function getDeclarationForComponentDeclaration( + declaration: ComponentDeclaration, + result: AnalyzerResult, + context: TransformerContext +): schema.Declaration | undefined { + if (declaration.kind === "interface") { + return undefined; + } + + const superClassClause = getSuperclassHeritageClause(declaration); + const superClass = superClassClause ? getReferenceFromHeritageClause(superClassClause, context) : undefined; + const definition = result.componentDefinitions.find(def => def.declaration?.node === declaration.node); + const mixinClauses = getMixinHeritageClauses(declaration); + const mixins = mixinClauses.map(c => getReferenceFromHeritageClause(c, context)).filter((c): c is schema.Reference => c !== undefined); + const members = getClassMembersForDeclaration(declaration, context); + const name = declaration.symbol?.name ?? getNodeName(declaration.node, { ts: tsModule }); + + if (!name) { + return undefined; + } + + const classDecl: schema.ClassDeclaration = { + kind: "class", + name, + superclass: superClass, + mixins: mixins.length > 0 ? mixins : undefined, + description: declaration.jsDoc?.description, + members: members.length > 0 ? members : undefined, + summary: getSummaryFromJsDoc(declaration.jsDoc) + }; + + if (!definition) { + return classDecl; + } + + // TODO (43081j); compute remaining extra properties for when this is a + // custom element class + return classDecl; } function* getCustomElementExportsFromResult(result: AnalyzerResult, context: TransformerContext): IterableIterator { @@ -204,60 +249,6 @@ function* getExportedDeclarationsFromResult(result: AnalyzerResult, context: Tra } } -/** - * Returns classes in an analyzer result - * @param result - * @param context - */ -function* getClassesFromResult(result: AnalyzerResult, context: TransformerContext): IterableIterator { - if (result.declarations) { - for (const decl of result.declarations) { - const doc = getDeclarationFromDeclaration(decl, result, context); - if (doc) { - yield doc; - } - } - } -} - -/** - * Converts a component declaration to schema declaration - * @param declaration - * @param result - * @param context - */ -function getDeclarationFromDeclaration( - declaration: ComponentDeclaration, - result: AnalyzerResult, - context: TransformerContext -): schema.Declaration | undefined { - if (declaration.kind === "interface") { - return undefined; - } - - // Get the superclass of this declaration - const superclassHeritage = getSuperclassHeritageClause(declaration); - const superclassRef = superclassHeritage === undefined ? undefined : getReferenceFromHeritageClause(superclassHeritage, context); - - // Get all mixins - const mixinHeritage = getMixinHeritageClauses(declaration); - const mixinRefs = arrayDefined(mixinHeritage.map(h => getReferenceFromHeritageClause(h, context))); - - const members = getClassMembersForDeclaration(declaration, context); - - const classDecl: schema.ClassDeclaration = { - kind: "class", - superclass: superclassRef, - mixins: mixinRefs.length > 0 ? mixinRefs : undefined, - description: declaration.jsDoc?.description, - name: declaration.symbol?.name || getNodeName(declaration.node, { ts: tsModule }) || "", - members: members.length > 0 ? members : undefined, - summary: getSummaryFromJsDoc(declaration.jsDoc) - }; - - return classDecl; -} - /** * Returns class member docs for a declaration * @param declaration From e7db5150f31cf90042e7c429060e6b165518e460 Mon Sep 17 00:00:00 2001 From: 43081j <43081j@users.noreply.github.com> Date: Sat, 3 Jul 2021 12:53:55 +0100 Subject: [PATCH 05/13] add element declarations and clean up the code --- .../custom-elements-manifest/context.ts | 7 + .../get-declarations.ts | 325 +++++++++++++ .../custom-elements-manifest/get-exports.ts | 46 ++ .../custom-elements-manifest/schema.ts | 96 ++-- .../custom-elements-manifest/transformer.ts | 457 +----------------- .../custom-elements-manifest/utils.ts | 180 +++++++ 6 files changed, 630 insertions(+), 481 deletions(-) create mode 100644 src/transformers/custom-elements-manifest/context.ts create mode 100644 src/transformers/custom-elements-manifest/get-declarations.ts create mode 100644 src/transformers/custom-elements-manifest/get-exports.ts create mode 100644 src/transformers/custom-elements-manifest/utils.ts diff --git a/src/transformers/custom-elements-manifest/context.ts b/src/transformers/custom-elements-manifest/context.ts new file mode 100644 index 00000000..d353f43c --- /dev/null +++ b/src/transformers/custom-elements-manifest/context.ts @@ -0,0 +1,7 @@ +// TODO (43081j): use a common type instead of this for all transformers +export interface TransformerContext { + config: TransformerConfig; + checker: TypeChecker; + program: Program; + ts: typeof tsModule; +} diff --git a/src/transformers/custom-elements-manifest/get-declarations.ts b/src/transformers/custom-elements-manifest/get-declarations.ts new file mode 100644 index 00000000..92eb0dd5 --- /dev/null +++ b/src/transformers/custom-elements-manifest/get-declarations.ts @@ -0,0 +1,325 @@ +import * as tsModule from "typescript"; +import { isSimpleType, toSimpleType } from "ts-simple-type"; +import { ComponentDeclaration } from "../../analyze/types/component-declaration"; +import { getMixinHeritageClauses, getSuperclassHeritageClause } from "../../analyze/util/component-declaration-util"; +import { findParent, getNodeName } from "../../analyze/util/ast-util"; +import { getJsDoc } from "../../analyze/util/js-doc-util"; +import { getTypeHintFromType } from "../../util/get-type-hint-from-type"; +import { AnalyzerResult } from "../../analyze/types/analyzer-result"; +import * as schema from "./schema"; +import { TransformerContext } from "./context"; +import { + typeToSchemaType, + getSummaryFromJsDoc, + getParameterFromJsDoc, + getReturnFromJsDoc, + getReferenceFromHeritageClause, + getInheritedFromReference +} from "./utils"; + +/** + * Returns variables in an analyzer result + * @param result + * @param context + */ +function* getExportedDeclarationsFromResult(result: AnalyzerResult, context: TransformerContext): IterableIterator { + // Get all export symbols in the source file + const symbol = context.checker.getSymbolAtLocation(result.sourceFile); + if (symbol == null) { + return; + } + + const exports = context.checker.getExportsOfModule(symbol); + + // Convert all export variables to VariableDocs + for (const exp of exports) { + const node = exp.valueDeclaration; + + switch (exp.flags) { + case tsModule.SymbolFlags.BlockScopedVariable: + case tsModule.SymbolFlags.Variable: { + if (tsModule.isVariableDeclaration(node)) { + // Get the nearest variable statement in order to read the jsdoc + const variableStatement = findParent(node, tsModule.isVariableStatement) || node; + const jsDoc = getJsDoc(variableStatement, tsModule); + + yield { + kind: "variable", + name: node.name.getText(), + description: jsDoc?.description, + type: typeToSchemaType(context, context.checker.getTypeAtLocation(node)), + summary: getSummaryFromJsDoc(jsDoc) + }; + } + break; + } + case tsModule.SymbolFlags.Function: { + if (tsModule.isFunctionDeclaration(node) && node.name) { + const jsDoc = getJsDoc(node, tsModule); + const parameters: schema.Parameter[] = []; + let returnType: tsModule.Type | undefined = undefined; + + for (const param of node.parameters) { + const name = param.name.getText(); + const { description, typeHint } = getParameterFromJsDoc(name, jsDoc); + const type = typeToSchemaType(context, typeHint || (param.type != null ? context.checker.getTypeAtLocation(param.type) : undefined)); + + parameters.push({ + name, + type, + description, + optional: param.questionToken !== undefined + }); + } + + const signature = context.checker.getSignatureFromDeclaration(node); + if (signature != null) { + returnType = context.checker.getReturnTypeOfSignature(signature); + } + + const { description: returnDescription, typeHint: returnTypeHint } = getReturnFromJsDoc(jsDoc); + + yield { + kind: "function", + name: node.name.getText(), + description: jsDoc?.description, + summary: getSummaryFromJsDoc(jsDoc), + parameters, + return: { + type: typeToSchemaType(context, returnTypeHint || returnType), + description: returnDescription + } + }; + } + break; + } + } + } +} + +/** + * Returns fields from a declaration + * @param declaration + * @param context + */ +function* getClassFieldsForDeclaration(declaration: ComponentDeclaration, context: TransformerContext): IterableIterator { + const visibility = context.config.visibility ?? "public"; + for (const member of declaration.members) { + if (member.visibility === visibility && member.propName != null) { + yield { + kind: "field", + name: member.propName, + privacy: member.visibility, + description: member.jsDoc?.description, + type: typeToSchemaType(context, member.typeHint || member.type?.()), + default: member.default != null ? JSON.stringify(member.default) : undefined, + inheritedFrom: getInheritedFromReference(declaration, member, context), + summary: getSummaryFromJsDoc(member.jsDoc) + // TODO: "static" + }; + } + } +} + +/** + * Returns method docs for a declaration + * @param declaration + * @param context + */ +function* getMethodsForDeclaration(declaration: ComponentDeclaration, context: TransformerContext): IterableIterator { + const visibility = context.config.visibility ?? "public"; + for (const method of declaration.methods) { + const parameters: schema.Parameter[] = []; + const node = method.node; + let returnType: tsModule.Type | undefined = undefined; + + if (method.visibility === visibility && node !== undefined && tsModule.isMethodDeclaration(node)) { + for (const param of node.parameters) { + const name = param.name.getText(); + const { description, typeHint } = getParameterFromJsDoc(name, method.jsDoc); + + parameters.push({ + name: name, + type: typeToSchemaType(context, typeHint || (param.type != null ? context.checker.getTypeAtLocation(param.type) : undefined)), + description: description, + optional: param.questionToken !== undefined + }); + } + + // Get return type + const signature = context.checker.getSignatureFromDeclaration(node); + if (signature != null) { + returnType = context.checker.getReturnTypeOfSignature(signature); + } + } + + // Get return info from jsdoc + const { description: returnDescription, typeHint: returnTypeHint } = getReturnFromJsDoc(method.jsDoc); + + yield { + kind: "method", + name: method.name, + privacy: method.visibility, + description: method.jsDoc?.description, + parameters, + return: { + description: returnDescription, + type: typeToSchemaType(context, returnTypeHint || returnType) + }, + inheritedFrom: getInheritedFromReference(declaration, method, context), + summary: getSummaryFromJsDoc(method.jsDoc) + // TODO: "static" + }; + } +} + +/** + * Returns class member docs for a declaration + * @param declaration + * @param context + */ +function getClassMembersForDeclaration(declaration: ComponentDeclaration, context: TransformerContext): schema.ClassMember[] { + return [...getClassFieldsForDeclaration(declaration, context), ...getMethodsForDeclaration(declaration, context)]; +} + +function* getEventsFromDeclaration(declaration: ComponentDeclaration, context: TransformerContext): IterableIterator { + const visibility = context.config.visibility ?? "public"; + for (const event of declaration.events) { + if (event.visibility === visibility) { + const type = event.type?.() ?? { kind: "ANY" }; + const simpleType = isSimpleType(type) ? type : toSimpleType(type, context.checker); + const typeName = simpleType.kind === "GENERIC_ARGUMENTS" ? simpleType.target.name : simpleType.name; + yield { + description: event.jsDoc?.description, + name: event.name, + inheritedFrom: getInheritedFromReference(declaration, event, context), + type: typeName === null || typeName === undefined || simpleType.kind === "ANY" ? { text: "Event" } : { text: typeName } + }; + } + } +} + +function* getSlotsFromDeclaration(declaration: ComponentDeclaration, context: TransformerContext): IterableIterator { + for (const slot of declaration.slots) { + yield { + description: slot.jsDoc?.description, + name: slot.name ?? "" + }; + } +} + +function* getAttributesFromDeclaration(declaration: ComponentDeclaration, context: TransformerContext): IterableIterator { + const visibility = context.config.visibility ?? "public"; + for (const member of declaration.members) { + if (member.visibility === visibility && member.attrName) { + const type = getTypeHintFromType(member.typeHint ?? member.type?.(), context.checker, context.config); + yield { + name: member.attrName, + fieldName: member.propName, + default: member.default != null ? JSON.stringify(member.default) : undefined, + description: member.jsDoc?.description, + type: type === undefined ? undefined : { text: type }, + inheritedFrom: getInheritedFromReference(declaration, member, context) + }; + } + } +} + +function* getCSSPropertiesFromDeclaration( + declaration: ComponentDeclaration, + context: TransformerContext +): IterableIterator { + for (const cssProperty of declaration.cssProperties) { + // TODO (43081j): somehow populate the syntax property + yield { + name: cssProperty.name, + description: cssProperty.jsDoc?.description, + default: cssProperty.default != null ? JSON.stringify(cssProperty.default) : undefined + }; + } +} + +function* getCSSPartsFromDeclaration(declaration: ComponentDeclaration, context: TransformerContext): IterableIterator { + for (const cssPart of declaration.cssParts) { + yield { + name: cssPart.name, + description: cssPart.jsDoc?.description + }; + } +} + +function getDeclarationForComponentDeclaration( + declaration: ComponentDeclaration, + result: AnalyzerResult, + context: TransformerContext +): schema.Declaration | undefined { + if (declaration.kind === "interface") { + return undefined; + } + + const superClassClause = getSuperclassHeritageClause(declaration); + const superClass = superClassClause ? getReferenceFromHeritageClause(superClassClause, context) : undefined; + const definition = result.componentDefinitions.find(def => def.declaration?.node === declaration.node); + const mixinClauses = getMixinHeritageClauses(declaration); + const mixins = mixinClauses.map(c => getReferenceFromHeritageClause(c, context)).filter((c): c is schema.Reference => c !== undefined); + const members = getClassMembersForDeclaration(declaration, context); + const name = declaration.symbol?.name ?? getNodeName(declaration.node, { ts: tsModule }); + + if (!name) { + return undefined; + } + + const classDecl: schema.ClassDeclaration = { + kind: "class", + name, + superclass: superClass, + mixins: mixins.length > 0 ? mixins : undefined, + description: declaration.jsDoc?.description, + members: members.length > 0 ? members : undefined, + summary: getSummaryFromJsDoc(declaration.jsDoc) + }; + + if (!definition) { + return classDecl; + } + + const events = [...getEventsFromDeclaration(declaration, context)]; + const slots = [...getSlotsFromDeclaration(declaration, context)]; + const attributes = [...getAttributesFromDeclaration(declaration, context)]; + const cssProperties = [...getCSSPropertiesFromDeclaration(declaration, context)]; + const cssParts = [...getCSSPartsFromDeclaration(declaration, context)]; + + // Return a custom element doc if a definition was found + const customElementDoc: schema.CustomElementDeclaration = { + ...classDecl, + customElement: true, + tagName: definition.tagName, + events: events.length > 0 ? events : undefined, + slots: slots.length > 0 ? slots : undefined, + attributes: attributes.length > 0 ? attributes : undefined, + cssProperties: cssProperties.length > 0 ? cssProperties : undefined, + cssParts: cssParts.length > 0 ? cssParts : undefined + }; + + return customElementDoc; +} + +function* getDeclarationsForResult(result: AnalyzerResult, context: TransformerContext): IterableIterator { + if (result.declarations) { + for (const decl of result.declarations) { + const schemaDecl = getDeclarationForComponentDeclaration(decl, result, context); + if (schemaDecl) { + yield schemaDecl; + } + } + } +} + +/** + * Returns declarations in an analyzer result + * @param result + * @param context + */ +export function getDeclarationsFromResult(result: AnalyzerResult, context: TransformerContext): schema.Declaration[] { + return [...getExportedDeclarationsFromResult(result, context), ...getDeclarationsForResult(result, context)]; +} diff --git a/src/transformers/custom-elements-manifest/get-exports.ts b/src/transformers/custom-elements-manifest/get-exports.ts new file mode 100644 index 00000000..be378c47 --- /dev/null +++ b/src/transformers/custom-elements-manifest/get-exports.ts @@ -0,0 +1,46 @@ +import { AnalyzerResult } from "../../analyze/types/analyzer-result"; +import { TransformerContext } from "./context"; +import * as schema from "./schema"; +import { getReferenceForNode } from "./utils"; + +function* getCustomElementExportsFromResult(result: AnalyzerResult, context: TransformerContext): IterableIterator { + for (const definition of result.componentDefinitions) { + // It's not possible right now to model a tag name where the + // declaration couldn't be resolved because the "declaration" is required + if (definition.declaration == null) { + continue; + } + + yield { + kind: "custom-element-definition", + name: definition.tagName, + declaration: getReferenceForNode(definition.declaration.node, context) + }; + } +} + +function* getExportedNamesFromResult(result: AnalyzerResult, context: TransformerContext): IterableIterator { + const symbol = context.checker.getSymbolAtLocation(result.sourceFile); + if (symbol == null) { + return; + } + + const exports = context.checker.getExportsOfModule(symbol); + + for (const exp of exports) { + yield { + kind: "js", + name: exp.name, + declaration: getReferenceForNode(exp.valueDeclaration, context) + }; + } +} + +/** + * Returns exports in an analyzer result + * @param result + * @param context + */ +export function getExportsFromResult(result: AnalyzerResult, context: TransformerContext): schema.Export[] { + return [...getCustomElementExportsFromResult(result, context), ...getExportedNamesFromResult(result, context)]; +} diff --git a/src/transformers/custom-elements-manifest/schema.ts b/src/transformers/custom-elements-manifest/schema.ts index d9891f6c..f109ffb7 100644 --- a/src/transformers/custom-elements-manifest/schema.ts +++ b/src/transformers/custom-elements-manifest/schema.ts @@ -95,7 +95,7 @@ export interface JavaScriptExport { * * - Default exports must use the name "default". * - Named exports use the name that is exported. If the export is renamed - * with the "as" clause, use the exported name. + * with the "as" clause, use the exported name. * - Aggregating exports (`* from`) should use the name `*` */ name: string; @@ -131,7 +131,13 @@ export interface CustomElementExport { declaration: Reference; } -export type Declaration = ClassDeclaration | FunctionDeclaration | MixinDeclaration | VariableDeclaration | CustomElementDeclaration; +export type Declaration = + | ClassDeclaration + | FunctionDeclaration + | MixinDeclaration + | VariableDeclaration + | CustomElementDeclaration + | CustomElementMixinDeclaration; /** * A reference to an export of a module. @@ -173,16 +179,16 @@ export interface SourceReference { * neccessarily part of a custom element class, but belong to the definition * (often called the "registration") or the `customElements.define()` call. * - * Because classes and tag anmes can only be registered once, there's a + * Because classes and tag names can only be registered once, there's a * one-to-one relationship between classes and tag names. For ease of use, * we allow the tag name here. * * Some packages define and register custom elements in separate modules. In * these cases one `Module` should contain the `CustomElement` without a * tagName, and another `Module` should contain the - * `CustomElement`. + * `CustomElementExport`. */ -export interface CustomElementDeclaration extends ClassDeclaration {} +export interface CustomElementDeclaration extends ClassDeclaration, CustomElement {} /** * The additional fields that a custom element adds to classes and mixins. @@ -217,6 +223,12 @@ export interface CustomElement extends ClassLike { cssProperties?: CssCustomProperty[]; demos?: Demo[]; + + /** + * Distinguishes a regular JavaScript class from a + * custom element class + */ + customElement: true; } export interface Attribute { @@ -314,6 +326,21 @@ export interface CssCustomProperty { */ name: string; + /** + * The expected syntax of the defined property. Defaults to "*". + * + * The syntax must be a valid CSS [syntax string](https://developer.mozilla.org/en-US/docs/Web/CSS/@property/syntax) + * as defined in the CSS Properties and Values API. + * + * Examples: + * + * "": accepts a color + * " | ": accepts lengths or percentages but not calc expressions with a combination of the two + * "small | medium | large": accepts one of these values set as custom idents. + * "*": any valid token + */ + syntax?: string; + default?: string; /** @@ -408,18 +435,18 @@ export interface ClassLike { * is described by: * ```json * { - * "kind": "class", - * "superclass": { - * "name": "S" - * }, - * "mixins": [ - * { - * "name": "A" - * }, - * { - * "name": "B" - * }, - * ] + * "kind": "class", + * "superclass": { + * "name": "S" + * }, + * "mixins": [ + * { + * "name": "A" + * }, + * { + * "name": "B" + * }, + * ] * } * ``` */ @@ -502,33 +529,38 @@ export interface ClassMethod extends FunctionLike { * This JavaScript mixin declaration: * ```javascript * const MyMixin = (base) => class extends base { - * foo() { ... } + * foo() { ... } * } * ``` * * Is described by this JSON: * ```json * { - * "kind": "mixin", - * "name": "MyMixin", - * "parameters": [ - * { - * "name": "base", - * } - * ], - * "members": [ - * { - * "kind": "method", - * "name": "foo", - * } - * ] + * "kind": "mixin", + * "name": "MyMixin", + * "parameters": [ + * { + * "name": "base", + * } + * ], + * "members": [ + * { + * "kind": "method", + * "name": "foo", + * } + * ] * } * ``` */ -export interface MixinDeclaration extends CustomElement, FunctionLike { +export interface MixinDeclaration extends ClassLike, FunctionLike { kind: "mixin"; } +/** + * A class mixin that also adds custom element related properties. + */ +export interface CustomElementMixinDeclaration extends MixinDeclaration, CustomElement {} + export interface VariableDeclaration extends PropertyLike { kind: "variable"; source?: SourceReference; diff --git a/src/transformers/custom-elements-manifest/transformer.ts b/src/transformers/custom-elements-manifest/transformer.ts index f2d75cd3..5a49054e 100644 --- a/src/transformers/custom-elements-manifest/transformer.ts +++ b/src/transformers/custom-elements-manifest/transformer.ts @@ -1,26 +1,15 @@ -import { basename, relative } from "path"; import * as tsModule from "typescript"; -import { SimpleType } from "ts-simple-type"; -import { Node, Program, SourceFile, Type, TypeChecker } from "typescript"; +import { Program, SourceFile } from "typescript"; import { AnalyzerResult } from "../../analyze/types/analyzer-result"; -import { ComponentDeclaration, ComponentHeritageClause } from "../../analyze/types/component-declaration"; -import { ComponentFeatureBase } from "../../analyze/types/features/component-feature"; -import { JsDoc } from "../../analyze/types/js-doc"; -import { findParent, getNodeName, resolveDeclarations } from "../../analyze/util/ast-util"; -import { getMixinHeritageClauses, getSuperclassHeritageClause, visitAllHeritageClauses } from "../../analyze/util/component-declaration-util"; -import { getJsDoc } from "../../analyze/util/js-doc-util"; -import { getTypeHintFromType } from "../../util/get-type-hint-from-type"; -import { filterVisibility } from "../../util/model-util"; +import { ComponentDeclaration } from "../../analyze/types/component-declaration"; +import { visitAllHeritageClauses } from "../../analyze/util/component-declaration-util"; import { TransformerConfig } from "../transformer-config"; import { TransformerFunction } from "../transformer-function"; import * as schema from "./schema"; - -interface TransformerContext { - config: TransformerConfig; - checker: TypeChecker; - program: Program; - ts: typeof tsModule; -} +import { TransformerContext } from "./context"; +import { getExportsFromResult } from "./get-exports"; +import { getDeclarationsFromResult } from "./get-declarations"; +import { getRelativePath } from "./utils"; /** * Transforms results to a custom elements manifest @@ -43,7 +32,7 @@ export const transformer: TransformerFunction = (results: AnalyzerResult[], prog const modules = flattenedAnalyzerResults.map(result => resultToModule(result, context)); const manifest: schema.Package = { - schemaVersion: "experimental", + schemaVersion: "1.0.0", modules }; @@ -67,410 +56,6 @@ function resultToModule(result: AnalyzerResult, context: TransformerContext): sc }; } -/** - * Returns exports in an analyzer result - * @param result - * @param context - */ -function getExportsFromResult(result: AnalyzerResult, context: TransformerContext): schema.Export[] { - return [...getCustomElementExportsFromResult(result, context), ...getExportedNamesFromResult(result, context)]; -} - -/** - * Returns declarations in an analyzer result - * @param result - * @param context - */ -function getDeclarationsFromResult(result: AnalyzerResult, context: TransformerContext): schema.Declaration[] { - return [...getExportedDeclarationsFromResult(result, context), ...getDeclarationsForResult(result, context)]; -} - -function* getDeclarationsForResult(result: AnalyzerResult, context: TransformerContext): IterableIterator { - if (result.declarations) { - for (const decl of result.declarations) { - const schemaDecl = getDeclarationForComponentDeclaration(decl, result, context); - if (schemaDecl) { - yield schemaDecl; - } - } - } -} - -function getDeclarationForComponentDeclaration( - declaration: ComponentDeclaration, - result: AnalyzerResult, - context: TransformerContext -): schema.Declaration | undefined { - if (declaration.kind === "interface") { - return undefined; - } - - const superClassClause = getSuperclassHeritageClause(declaration); - const superClass = superClassClause ? getReferenceFromHeritageClause(superClassClause, context) : undefined; - const definition = result.componentDefinitions.find(def => def.declaration?.node === declaration.node); - const mixinClauses = getMixinHeritageClauses(declaration); - const mixins = mixinClauses.map(c => getReferenceFromHeritageClause(c, context)).filter((c): c is schema.Reference => c !== undefined); - const members = getClassMembersForDeclaration(declaration, context); - const name = declaration.symbol?.name ?? getNodeName(declaration.node, { ts: tsModule }); - - if (!name) { - return undefined; - } - - const classDecl: schema.ClassDeclaration = { - kind: "class", - name, - superclass: superClass, - mixins: mixins.length > 0 ? mixins : undefined, - description: declaration.jsDoc?.description, - members: members.length > 0 ? members : undefined, - summary: getSummaryFromJsDoc(declaration.jsDoc) - }; - - if (!definition) { - return classDecl; - } - - // TODO (43081j); compute remaining extra properties for when this is a - // custom element class - return classDecl; -} - -function* getCustomElementExportsFromResult(result: AnalyzerResult, context: TransformerContext): IterableIterator { - for (const definition of result.componentDefinitions) { - // It's not possible right now to model a tag name where the - // declaration couldn't be resolved because the "declaration" is required - if (definition.declaration == null) { - continue; - } - - yield { - kind: "custom-element-definition", - name: definition.tagName, - declaration: getReferenceForNode(definition.declaration.node, context) - }; - } -} - -function* getExportedNamesFromResult(result: AnalyzerResult, context: TransformerContext): IterableIterator { - const symbol = context.checker.getSymbolAtLocation(result.sourceFile); - if (symbol == null) { - return; - } - - const exports = context.checker.getExportsOfModule(symbol); - - for (const exp of exports) { - yield { - kind: "js", - name: exp.name, - declaration: getReferenceForNode(exp.valueDeclaration, context) - }; - } -} - -/** - * Returns variables in an analyzer result - * @param result - * @param context - */ -function* getExportedDeclarationsFromResult(result: AnalyzerResult, context: TransformerContext): IterableIterator { - // Get all export symbols in the source file - const symbol = context.checker.getSymbolAtLocation(result.sourceFile); - if (symbol == null) { - return; - } - - const exports = context.checker.getExportsOfModule(symbol); - - // Convert all export variables to VariableDocs - for (const exp of exports) { - const node = exp.valueDeclaration; - - switch (exp.flags) { - case tsModule.SymbolFlags.BlockScopedVariable: - case tsModule.SymbolFlags.Variable: { - if (tsModule.isVariableDeclaration(node)) { - // Get the nearest variable statement in order to read the jsdoc - const variableStatement = findParent(node, tsModule.isVariableStatement) || node; - const jsDoc = getJsDoc(variableStatement, tsModule); - - yield { - kind: "variable", - name: node.name.getText(), - description: jsDoc?.description, - type: typeToSchemaType(context, context.checker.getTypeAtLocation(node)), - summary: getSummaryFromJsDoc(jsDoc) - }; - } - break; - } - case tsModule.SymbolFlags.Function: { - if (tsModule.isFunctionDeclaration(node) && node.name) { - const jsDoc = getJsDoc(node, tsModule); - const parameters: schema.Parameter[] = []; - let returnType: Type | undefined = undefined; - - for (const param of node.parameters) { - const name = param.name.getText(); - const { description, typeHint } = getParameterFromJsDoc(name, jsDoc); - const type = typeToSchemaType(context, typeHint || (param.type != null ? context.checker.getTypeAtLocation(param.type) : undefined)); - - parameters.push({ - name, - type, - description, - optional: param.questionToken !== undefined - }); - } - - const signature = context.checker.getSignatureFromDeclaration(node); - if (signature != null) { - returnType = context.checker.getReturnTypeOfSignature(signature); - } - - const { description: returnDescription, typeHint: returnTypeHint } = getReturnFromJsDoc(jsDoc); - - yield { - kind: "function", - name: node.name.getText(), - description: jsDoc?.description, - summary: getSummaryFromJsDoc(jsDoc), - parameters, - return: { - type: typeToSchemaType(context, returnTypeHint || returnType), - description: returnDescription - } - }; - } - break; - } - } - } -} - -/** - * Returns class member docs for a declaration - * @param declaration - * @param context - */ -function getClassMembersForDeclaration(declaration: ComponentDeclaration, context: TransformerContext): schema.ClassMember[] { - return [...getClassFieldsForDeclaration(declaration, context), ...getMethodsForDeclaration(declaration, context)]; -} - -/** - * Returns method docs for a declaration - * @param declaration - * @param context - */ -function* getMethodsForDeclaration(declaration: ComponentDeclaration, context: TransformerContext): IterableIterator { - for (const method of filterVisibility(context.config.visibility, declaration.methods)) { - const parameters: schema.Parameter[] = []; - const node = method.node; - let returnType: Type | undefined = undefined; - - if (node !== undefined && tsModule.isMethodDeclaration(node)) { - for (const param of node.parameters) { - const name = param.name.getText(); - const { description, typeHint } = getParameterFromJsDoc(name, method.jsDoc); - - parameters.push({ - name: name, - type: typeToSchemaType(context, typeHint || (param.type != null ? context.checker.getTypeAtLocation(param.type) : undefined)), - description: description, - optional: param.questionToken !== undefined - }); - } - - // Get return type - const signature = context.checker.getSignatureFromDeclaration(node); - if (signature != null) { - returnType = context.checker.getReturnTypeOfSignature(signature); - } - } - - // Get return info from jsdoc - const { description: returnDescription, typeHint: returnTypeHint } = getReturnFromJsDoc(method.jsDoc); - - yield { - kind: "method", - name: method.name, - privacy: method.visibility, - description: method.jsDoc?.description, - parameters, - return: { - description: returnDescription, - type: typeToSchemaType(context, returnTypeHint || returnType) - }, - inheritedFrom: getInheritedFromReference(declaration, method, context), - summary: getSummaryFromJsDoc(method.jsDoc) - // TODO: "static" - }; - } -} - -/** - * Returns fields from a declaration - * @param declaration - * @param context - */ -function* getClassFieldsForDeclaration(declaration: ComponentDeclaration, context: TransformerContext): IterableIterator { - for (const member of filterVisibility(context.config.visibility, declaration.members)) { - if (member.propName != null) { - yield { - kind: "field", - name: member.propName, - privacy: member.visibility, - description: member.jsDoc?.description, - type: typeToSchemaType(context, member.typeHint || member.type?.()), - default: member.default != null ? JSON.stringify(member.default) : undefined, - inheritedFrom: getInheritedFromReference(declaration, member, context), - summary: getSummaryFromJsDoc(member.jsDoc) - // TODO: "static" - }; - } - } -} - -function getInheritedFromReference( - onDeclaration: ComponentDeclaration, - feature: ComponentFeatureBase, - context: TransformerContext -): schema.Reference | undefined { - if (feature.declaration != null && feature.declaration !== onDeclaration) { - return getReferenceForNode(feature.declaration.node, context); - } - - return undefined; -} - -/** - * Returns a Reference to a node - * @param node - * @param context - */ -function getReferenceForNode(node: Node, context: TransformerContext): schema.Reference { - const sourceFile = node.getSourceFile(); - const name = getNodeName(node, context) as string; - - // Test if the source file is from a typescript lib - // TODO: Find a better way of checking this - const isLib = sourceFile.isDeclarationFile && sourceFile.fileName.match(/typescript\/lib.*\.d\.ts$/) != null; - if (isLib) { - // Only return the name of the declaration if it's from lib - return { - name - }; - } - - // Test if the source file is located in a package - const packageName = getPackageName(sourceFile); - if (packageName != null) { - return { - name, - package: packageName - }; - } - - // Get the module path name - const module = getRelativePath(sourceFile.fileName, context); - return { - name, - module - }; -} - -/** - * Returns the name of the package (if any) - * @param sourceFile - */ -function getPackageName(sourceFile: SourceFile): string | undefined { - // TODO: Make it possible to access the ModuleResolutionHost - // in order to resolve the package using "resolveModuleNames" - // The following approach is very, very naive and is only temporary. - const match = sourceFile.fileName.match(/node_modules\/(.*?)\//); - - if (match != null) { - return match[1]; - } - - return undefined; -} - -/** - * Returns a relative path based on "cwd" in the config - * @param fullPath - * @param context - */ -function getRelativePath(fullPath: string, context: TransformerContext) { - return context.config.cwd != null ? `./${relative(context.config.cwd, fullPath)}` : basename(fullPath); -} - -/** - * Returns description and typeHint based on jsdoc for a specific parameter name - * @param name - * @param jsDoc - */ -function getParameterFromJsDoc(name: string, jsDoc: JsDoc | undefined): { description?: string; typeHint?: string } { - if (jsDoc?.tags == undefined) { - return {}; - } - - for (const tag of jsDoc.tags) { - const parsed = tag.parsed(); - - if (parsed.tag === "param" && parsed.name === name) { - return { description: parsed.description, typeHint: parsed.type }; - } - } - - return {}; -} - -/** - * Get return description and return typeHint from jsdoc - * @param jsDoc - */ -function getReturnFromJsDoc(jsDoc: JsDoc | undefined): { description?: string; typeHint?: string } { - const tag = jsDoc?.tags?.find(tag => ["returns", "return"].includes(tag.tag)); - - if (tag == null) { - return {}; - } - - const parsed = tag.parsed(); - return { description: parsed.description, typeHint: parsed.type }; -} - -/** - * Converts a heritage clause into a reference - * @param heritage - * @param context - */ -function getReferenceFromHeritageClause(heritage: ComponentHeritageClause, context: TransformerContext): schema.Reference | undefined { - const node = heritage.declaration?.node; - const identifier = heritage.identifier; - - // Return a reference for this node if any - if (node != null) { - return getReferenceForNode(node, context); - } - - // Try to get declaration of the identifier if no node was found - const [declaration] = resolveDeclarations(identifier, context); - if (declaration != null) { - return getReferenceForNode(declaration, context); - } - - // Just return the name of the reference if nothing could be resolved - const name = getNodeName(identifier, context); - if (name != null) { - return { name }; - } - - return undefined; -} - /** * Flatten all analyzer results with inherited declarations * @param results @@ -519,29 +104,3 @@ function flattenAnalyzerResults(results: AnalyzerResult[]): AnalyzerResult[] { }; }); } - -/** - * Returns the content of the summary jsdoc tag if any - * @param jsDoc - */ -function getSummaryFromJsDoc(jsDoc: JsDoc | undefined): string | undefined { - const summaryTag = jsDoc?.tags?.find(tag => tag.tag === "summary"); - - if (summaryTag == null) { - return undefined; - } - - return summaryTag.comment; -} - -function typeToSchemaType(context: TransformerContext, type: string | Type | SimpleType | undefined): schema.Type | undefined { - const hint = getTypeHintFromType(type, context.checker, context.config); - - if (!hint) { - return undefined; - } - - return { - text: hint - }; -} diff --git a/src/transformers/custom-elements-manifest/utils.ts b/src/transformers/custom-elements-manifest/utils.ts new file mode 100644 index 00000000..fccea4ad --- /dev/null +++ b/src/transformers/custom-elements-manifest/utils.ts @@ -0,0 +1,180 @@ +import { basename, relative } from "path"; +import { SimpleType } from "ts-simple-type"; +import { Node, SourceFile, Type } from "typescript"; +import { TransformerContext } from "./context"; +import { JsDoc } from "../../analyze/types/js-doc"; +import * as schema from "./schema"; +import { getNodeName, resolveDeclarations } from "../../analyze/util/ast-util"; +import { ComponentDeclaration, ComponentHeritageClause } from "../../analyze/types/component-declaration"; +import { ComponentFeatureBase } from "../../analyze/types/features/component-feature"; +import { getTypeHintFromType } from "../../util/get-type-hint-from-type"; + +/** + * Returns a Reference to a node + * @param node + * @param context + */ +export function getReferenceForNode(node: Node, context: TransformerContext): schema.Reference { + const sourceFile = node.getSourceFile(); + const name = getNodeName(node, context) as string; + + // Test if the source file is from a typescript lib + // TODO: Find a better way of checking this + const isLib = sourceFile.isDeclarationFile && sourceFile.fileName.match(/typescript\/lib.*\.d\.ts$/) != null; + if (isLib) { + // Only return the name of the declaration if it's from lib + return { + name + }; + } + + // Test if the source file is located in a package + const packageName = getPackageName(sourceFile); + if (packageName != null) { + return { + name, + package: packageName + }; + } + + // Get the module path name + const module = getRelativePath(sourceFile.fileName, context); + return { + name, + module + }; +} + +export function getInheritedFromReference( + onDeclaration: ComponentDeclaration, + feature: ComponentFeatureBase, + context: TransformerContext +): schema.Reference | undefined { + if (feature.declaration != null && feature.declaration !== onDeclaration) { + return getReferenceForNode(feature.declaration.node, context); + } + + return undefined; +} + +/** + * Returns a relative path based on "cwd" in the config + * @param fullPath + * @param context + */ +export function getRelativePath(fullPath: string, context: TransformerContext): string { + return context.config.cwd != null ? `./${relative(context.config.cwd, fullPath)}` : basename(fullPath); +} + +/** + * Returns the name of the package (if any) + * @param sourceFile + */ +export function getPackageName(sourceFile: SourceFile): string | undefined { + // TODO: Make it possible to access the ModuleResolutionHost + // in order to resolve the package using "resolveModuleNames" + // The following approach is very, very naive and is only temporary. + const match = sourceFile.fileName.match(/node_modules\/(.*?)\//); + + if (match != null) { + return match[1]; + } + + return undefined; +} + +/** + * Returns description and typeHint based on jsdoc for a specific parameter name + * @param name + * @param jsDoc + */ +export function getParameterFromJsDoc(name: string, jsDoc: JsDoc | undefined): { description?: string; typeHint?: string } { + if (jsDoc?.tags == undefined) { + return {}; + } + + for (const tag of jsDoc.tags) { + const parsed = tag.parsed(); + + if (parsed.tag === "param" && parsed.name === name) { + return { description: parsed.description, typeHint: parsed.type }; + } + } + + return {}; +} + +/** + * Get return description and return typeHint from jsdoc + * @param jsDoc + */ +export function getReturnFromJsDoc(jsDoc: JsDoc | undefined): { description?: string; typeHint?: string } { + const tag = jsDoc?.tags?.find(tag => ["returns", "return"].includes(tag.tag)); + + if (tag == null) { + return {}; + } + + const parsed = tag.parsed(); + return { description: parsed.description, typeHint: parsed.type }; +} + +/** + * Converts a typescript type to a schema type + * @param context + * @param type + */ +export function typeToSchemaType(context: TransformerContext, type: string | Type | SimpleType | undefined): schema.Type | undefined { + const hint = getTypeHintFromType(type, context.checker, context.config); + + if (!hint) { + return undefined; + } + + return { + text: hint + }; +} + +/** + * Returns the content of the summary jsdoc tag if any + * @param jsDoc + */ +export function getSummaryFromJsDoc(jsDoc: JsDoc | undefined): string | undefined { + const summaryTag = jsDoc?.tags?.find(tag => tag.tag === "summary"); + + if (summaryTag == null) { + return undefined; + } + + return summaryTag.comment; +} + +/** + * Converts a heritage clause into a reference + * @param heritage + * @param context + */ +export function getReferenceFromHeritageClause(heritage: ComponentHeritageClause, context: TransformerContext): schema.Reference | undefined { + const node = heritage.declaration?.node; + const identifier = heritage.identifier; + + // Return a reference for this node if any + if (node != null) { + return getReferenceForNode(node, context); + } + + // Try to get declaration of the identifier if no node was found + const [declaration] = resolveDeclarations(identifier, context); + if (declaration != null) { + return getReferenceForNode(declaration, context); + } + + // Just return the name of the reference if nothing could be resolved + const name = getNodeName(identifier, context); + if (name != null) { + return { name }; + } + + return undefined; +} From 78ec6c1bbe7a3b271117245e2ec977bd870b6542 Mon Sep 17 00:00:00 2001 From: 43081j <43081j@users.noreply.github.com> Date: Sat, 3 Jul 2021 12:56:26 +0100 Subject: [PATCH 06/13] fix config types --- src/transformers/custom-elements-manifest/context.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/transformers/custom-elements-manifest/context.ts b/src/transformers/custom-elements-manifest/context.ts index d353f43c..9fdb7592 100644 --- a/src/transformers/custom-elements-manifest/context.ts +++ b/src/transformers/custom-elements-manifest/context.ts @@ -1,7 +1,10 @@ +import * as tsModule from "typescript"; +import { TransformerConfig } from "../transformer-config"; + // TODO (43081j): use a common type instead of this for all transformers export interface TransformerContext { config: TransformerConfig; - checker: TypeChecker; - program: Program; + checker: tsModule.TypeChecker; + program: tsModule.Program; ts: typeof tsModule; } From b73d7161bc380ac61717b818e6bc77852aa3a8f3 Mon Sep 17 00:00:00 2001 From: 43081j <43081j@users.noreply.github.com> Date: Sat, 3 Jul 2021 13:00:38 +0100 Subject: [PATCH 07/13] unify transformer interfaces --- src/transformers/custom-elements-manifest/context.ts | 10 ---------- .../custom-elements-manifest/get-declarations.ts | 2 +- .../custom-elements-manifest/get-exports.ts | 2 +- .../custom-elements-manifest/transformer.ts | 2 +- src/transformers/custom-elements-manifest/utils.ts | 2 +- src/transformers/json2/json2-transformer.ts | 10 ++-------- 6 files changed, 6 insertions(+), 22 deletions(-) delete mode 100644 src/transformers/custom-elements-manifest/context.ts diff --git a/src/transformers/custom-elements-manifest/context.ts b/src/transformers/custom-elements-manifest/context.ts deleted file mode 100644 index 9fdb7592..00000000 --- a/src/transformers/custom-elements-manifest/context.ts +++ /dev/null @@ -1,10 +0,0 @@ -import * as tsModule from "typescript"; -import { TransformerConfig } from "../transformer-config"; - -// TODO (43081j): use a common type instead of this for all transformers -export interface TransformerContext { - config: TransformerConfig; - checker: tsModule.TypeChecker; - program: tsModule.Program; - ts: typeof tsModule; -} diff --git a/src/transformers/custom-elements-manifest/get-declarations.ts b/src/transformers/custom-elements-manifest/get-declarations.ts index 92eb0dd5..0a86c05d 100644 --- a/src/transformers/custom-elements-manifest/get-declarations.ts +++ b/src/transformers/custom-elements-manifest/get-declarations.ts @@ -7,7 +7,7 @@ import { getJsDoc } from "../../analyze/util/js-doc-util"; import { getTypeHintFromType } from "../../util/get-type-hint-from-type"; import { AnalyzerResult } from "../../analyze/types/analyzer-result"; import * as schema from "./schema"; -import { TransformerContext } from "./context"; +import { TransformerContext } from "../transformer-context"; import { typeToSchemaType, getSummaryFromJsDoc, diff --git a/src/transformers/custom-elements-manifest/get-exports.ts b/src/transformers/custom-elements-manifest/get-exports.ts index be378c47..45b3760e 100644 --- a/src/transformers/custom-elements-manifest/get-exports.ts +++ b/src/transformers/custom-elements-manifest/get-exports.ts @@ -1,5 +1,5 @@ import { AnalyzerResult } from "../../analyze/types/analyzer-result"; -import { TransformerContext } from "./context"; +import { TransformerContext } from "../transformer-context"; import * as schema from "./schema"; import { getReferenceForNode } from "./utils"; diff --git a/src/transformers/custom-elements-manifest/transformer.ts b/src/transformers/custom-elements-manifest/transformer.ts index 5a49054e..7818f942 100644 --- a/src/transformers/custom-elements-manifest/transformer.ts +++ b/src/transformers/custom-elements-manifest/transformer.ts @@ -6,7 +6,7 @@ import { visitAllHeritageClauses } from "../../analyze/util/component-declaratio import { TransformerConfig } from "../transformer-config"; import { TransformerFunction } from "../transformer-function"; import * as schema from "./schema"; -import { TransformerContext } from "./context"; +import { TransformerContext } from "../transformer-context"; import { getExportsFromResult } from "./get-exports"; import { getDeclarationsFromResult } from "./get-declarations"; import { getRelativePath } from "./utils"; diff --git a/src/transformers/custom-elements-manifest/utils.ts b/src/transformers/custom-elements-manifest/utils.ts index fccea4ad..74572033 100644 --- a/src/transformers/custom-elements-manifest/utils.ts +++ b/src/transformers/custom-elements-manifest/utils.ts @@ -1,7 +1,7 @@ import { basename, relative } from "path"; import { SimpleType } from "ts-simple-type"; import { Node, SourceFile, Type } from "typescript"; -import { TransformerContext } from "./context"; +import { TransformerContext } from "../transformer-context"; import { JsDoc } from "../../analyze/types/js-doc"; import * as schema from "./schema"; import { getNodeName, resolveDeclarations } from "../../analyze/util/ast-util"; diff --git a/src/transformers/json2/json2-transformer.ts b/src/transformers/json2/json2-transformer.ts index 6c65bf1a..58e73c31 100644 --- a/src/transformers/json2/json2-transformer.ts +++ b/src/transformers/json2/json2-transformer.ts @@ -1,7 +1,7 @@ import { basename, relative } from "path"; import { isSimpleType, toSimpleType } from "ts-simple-type"; import * as tsModule from "typescript"; -import { Node, Program, SourceFile, Type, TypeChecker } from "typescript"; +import { Node, Program, SourceFile, Type } from "typescript"; import { AnalyzerResult } from "../../analyze/types/analyzer-result"; import { ComponentDeclaration, ComponentHeritageClause } from "../../analyze/types/component-declaration"; import { ComponentFeatureBase } from "../../analyze/types/features/component-feature"; @@ -36,13 +36,7 @@ import { SlotDoc, VariableDoc } from "./schema"; - -interface TransformerContext { - config: TransformerConfig; - checker: TypeChecker; - program: Program; - ts: typeof tsModule; -} +import { TransformerContext } from "../transformer-context"; /** * Transforms results to json using the schema found in the PR at https://github.com/webcomponents/custom-elements-json/pull/9 From 7d06011d675d3ccc201cc1e80b6fb20e224240bc Mon Sep 17 00:00:00 2001 From: 43081j <43081j@users.noreply.github.com> Date: Sat, 3 Jul 2021 14:24:22 +0100 Subject: [PATCH 08/13] discover exported declarations also --- .../custom-elements-manifest/get-declarations.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/transformers/custom-elements-manifest/get-declarations.ts b/src/transformers/custom-elements-manifest/get-declarations.ts index 0a86c05d..ba3b5fee 100644 --- a/src/transformers/custom-elements-manifest/get-declarations.ts +++ b/src/transformers/custom-elements-manifest/get-declarations.ts @@ -304,7 +304,7 @@ function getDeclarationForComponentDeclaration( return customElementDoc; } -function* getDeclarationsForResult(result: AnalyzerResult, context: TransformerContext): IterableIterator { +function* getComponentDeclarationsFromResult(result: AnalyzerResult, context: TransformerContext): IterableIterator { if (result.declarations) { for (const decl of result.declarations) { const schemaDecl = getDeclarationForComponentDeclaration(decl, result, context); @@ -313,6 +313,15 @@ function* getDeclarationsForResult(result: AnalyzerResult, context: TransformerC } } } + + for (const definition of result.componentDefinitions) { + if (definition.declaration) { + const schemaDecl = getDeclarationForComponentDeclaration(definition.declaration, result, context); + if (schemaDecl) { + yield schemaDecl; + } + } + } } /** @@ -321,5 +330,5 @@ function* getDeclarationsForResult(result: AnalyzerResult, context: TransformerC * @param context */ export function getDeclarationsFromResult(result: AnalyzerResult, context: TransformerContext): schema.Declaration[] { - return [...getExportedDeclarationsFromResult(result, context), ...getDeclarationsForResult(result, context)]; + return [...getExportedDeclarationsFromResult(result, context), ...getComponentDeclarationsFromResult(result, context)]; } From 9ab4306e0f248f997929b791f1d2bbd5120a674e Mon Sep 17 00:00:00 2001 From: 43081j <43081j@users.noreply.github.com> Date: Sun, 4 Jul 2021 18:11:04 +0100 Subject: [PATCH 09/13] add missing context --- src/transformers/transformer-context.ts | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/transformers/transformer-context.ts diff --git a/src/transformers/transformer-context.ts b/src/transformers/transformer-context.ts new file mode 100644 index 00000000..5e453193 --- /dev/null +++ b/src/transformers/transformer-context.ts @@ -0,0 +1,9 @@ +import * as tsModule from "typescript"; +import { TransformerConfig } from "./transformer-config"; + +export interface TransformerContext { + config: TransformerConfig; + checker: tsModule.TypeChecker; + program: tsModule.Program; + ts: typeof tsModule; +} From 8819be6635f14d33595688d19c7a24a0d47978e3 Mon Sep 17 00:00:00 2001 From: 43081j <43081j@users.noreply.github.com> Date: Mon, 19 Jul 2021 22:06:19 +0100 Subject: [PATCH 10/13] pr feedback --- package-lock.json | 11 + package.json | 1 + .../get-declarations.ts | 340 ++++++---- .../custom-elements-manifest/get-exports.ts | 30 +- .../custom-elements-manifest/schema.ts | 617 ------------------ .../custom-elements-manifest/transformer.ts | 49 +- .../custom-elements-manifest/utils.ts | 61 +- src/transformers/index.ts | 6 +- 8 files changed, 325 insertions(+), 790 deletions(-) delete mode 100644 src/transformers/custom-elements-manifest/schema.ts diff --git a/package-lock.json b/package-lock.json index cb400490..157cb0a8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "2.0.0-next.3", "license": "MIT", "dependencies": { + "custom-elements-manifest": "^1.0.0", "fast-glob": "^3.2.2", "ts-simple-type": "2.0.0-next.0", "typescript": "^4.4.3", @@ -1540,6 +1541,11 @@ "node": ">=0.10.0" } }, + "node_modules/custom-elements-manifest": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/custom-elements-manifest/-/custom-elements-manifest-1.0.0.tgz", + "integrity": "sha512-j59k0ExGCKA8T6Mzaq+7axc+KVHwpEphEERU7VZ99260npu/p/9kd+Db+I3cGKxHkM5y6q5gnlXn00mzRQkX2A==" + }, "node_modules/date-time": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/date-time/-/date-time-3.1.0.tgz", @@ -6541,6 +6547,11 @@ "array-find-index": "^1.0.1" } }, + "custom-elements-manifest": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/custom-elements-manifest/-/custom-elements-manifest-1.0.0.tgz", + "integrity": "sha512-j59k0ExGCKA8T6Mzaq+7axc+KVHwpEphEERU7VZ99260npu/p/9kd+Db+I3cGKxHkM5y6q5gnlXn00mzRQkX2A==" + }, "date-time": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/date-time/-/date-time-3.1.0.tgz", diff --git a/package.json b/package.json index 14bd33ec..8f688a23 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ }, "homepage": "https://github.com/runem/web-component-analyzer#readme", "dependencies": { + "custom-elements-manifest": "^1.0.0", "fast-glob": "^3.2.2", "ts-simple-type": "2.0.0-next.0", "typescript": "^4.4.3", diff --git a/src/transformers/custom-elements-manifest/get-declarations.ts b/src/transformers/custom-elements-manifest/get-declarations.ts index ba3b5fee..e7a0f647 100644 --- a/src/transformers/custom-elements-manifest/get-declarations.ts +++ b/src/transformers/custom-elements-manifest/get-declarations.ts @@ -1,13 +1,26 @@ -import * as tsModule from "typescript"; -import { isSimpleType, toSimpleType } from "ts-simple-type"; -import { ComponentDeclaration } from "../../analyze/types/component-declaration"; -import { getMixinHeritageClauses, getSuperclassHeritageClause } from "../../analyze/util/component-declaration-util"; -import { findParent, getNodeName } from "../../analyze/util/ast-util"; -import { getJsDoc } from "../../analyze/util/js-doc-util"; -import { getTypeHintFromType } from "../../util/get-type-hint-from-type"; -import { AnalyzerResult } from "../../analyze/types/analyzer-result"; -import * as schema from "./schema"; -import { TransformerContext } from "../transformer-context"; +/** + * @fileoverview A collection of utilities for computing custom element + * manifest declarations and exports from a given analyzer result. + * + * The entrypoint here is the `getDeclarationsFromResult` function which + * is responsible for traversing the internal analyzer result model + * and mapping to the appropriate manifest model. + * + * For example, mapping known exports into a list of manifest declarations. + */ +import * as tsModule from 'typescript'; +import {isSimpleType, toSimpleType} from 'ts-simple-type'; +import {ComponentDeclaration} from '../../analyze/types/component-declaration'; +import { + getMixinHeritageClauses, + getSuperclassHeritageClause +} from '../../analyze/util/component-declaration-util'; +import {findParent, getNodeName} from '../../analyze/util/ast-util'; +import {getJsDoc} from '../../analyze/util/js-doc-util'; +import {getTypeHintFromType} from '../../util/get-type-hint-from-type'; +import {AnalyzerResult} from '../../analyze/types/analyzer-result'; +import * as schema from 'custom-elements-manifest'; +import {TransformerContext} from '../transformer-context'; import { typeToSchemaType, getSummaryFromJsDoc, @@ -15,99 +28,107 @@ import { getReturnFromJsDoc, getReferenceFromHeritageClause, getInheritedFromReference -} from "./utils"; +} from './utils'; /** - * Returns variables in an analyzer result + * Computes the exported symbols of a result (e.g. variables, functions, etc.) * @param result * @param context */ -function* getExportedDeclarationsFromResult(result: AnalyzerResult, context: TransformerContext): IterableIterator { - // Get all export symbols in the source file +function* getExportedDeclarationsFromResult( + result: AnalyzerResult, + context: TransformerContext +): IterableIterator { + // Get all exported symbols in the source file const symbol = context.checker.getSymbolAtLocation(result.sourceFile); + if (symbol == null) { return; } const exports = context.checker.getExportsOfModule(symbol); - // Convert all export variables to VariableDocs + // Iterate through exported symbols the typescript compiler detected for (const exp of exports) { const node = exp.valueDeclaration; + const variableFlags = tsModule.SymbolFlags.BlockScopedVariable | tsModule.SymbolFlags.Variable; - switch (exp.flags) { - case tsModule.SymbolFlags.BlockScopedVariable: - case tsModule.SymbolFlags.Variable: { - if (tsModule.isVariableDeclaration(node)) { - // Get the nearest variable statement in order to read the jsdoc - const variableStatement = findParent(node, tsModule.isVariableStatement) || node; - const jsDoc = getJsDoc(variableStatement, tsModule); - - yield { - kind: "variable", - name: node.name.getText(), - description: jsDoc?.description, - type: typeToSchemaType(context, context.checker.getTypeAtLocation(node)), - summary: getSummaryFromJsDoc(jsDoc) - }; - } - break; + // Produce the correct manifest declaration for each symbol + if (exp.flags & variableFlags && node && tsModule.isVariableDeclaration(node)) { + // Get the nearest variable statement in order to read the jsdoc + const variableStatement = findParent(node, tsModule.isVariableStatement) || node; + const jsDoc = getJsDoc(variableStatement, tsModule); + + yield { + kind: 'variable', + name: node.name.getText(), + description: jsDoc?.description, + type: typeToSchemaType(context, context.checker.getTypeAtLocation(node)), + summary: getSummaryFromJsDoc(jsDoc) + }; + } + + if ( + exp.flags & tsModule.SymbolFlags.Function && + tsModule.isFunctionDeclaration(node) && + node.name + ) { + const jsDoc = getJsDoc(node, tsModule); + const parameters: schema.Parameter[] = []; + let returnType: tsModule.Type | undefined = undefined; + + for (const param of node.parameters) { + const name = param.name.getText(); + const {description, typeHint} = getParameterFromJsDoc(name, jsDoc); + const type = typeToSchemaType( + context, + typeHint ?? (param.type != null ? context.checker.getTypeAtLocation(param.type) : undefined) + ); + + parameters.push({ + name, + type, + description, + optional: param.questionToken !== undefined + }); } - case tsModule.SymbolFlags.Function: { - if (tsModule.isFunctionDeclaration(node) && node.name) { - const jsDoc = getJsDoc(node, tsModule); - const parameters: schema.Parameter[] = []; - let returnType: tsModule.Type | undefined = undefined; - - for (const param of node.parameters) { - const name = param.name.getText(); - const { description, typeHint } = getParameterFromJsDoc(name, jsDoc); - const type = typeToSchemaType(context, typeHint || (param.type != null ? context.checker.getTypeAtLocation(param.type) : undefined)); - - parameters.push({ - name, - type, - description, - optional: param.questionToken !== undefined - }); - } - - const signature = context.checker.getSignatureFromDeclaration(node); - if (signature != null) { - returnType = context.checker.getReturnTypeOfSignature(signature); - } - - const { description: returnDescription, typeHint: returnTypeHint } = getReturnFromJsDoc(jsDoc); - - yield { - kind: "function", - name: node.name.getText(), - description: jsDoc?.description, - summary: getSummaryFromJsDoc(jsDoc), - parameters, - return: { - type: typeToSchemaType(context, returnTypeHint || returnType), - description: returnDescription - } - }; - } - break; + + const signature = context.checker.getSignatureFromDeclaration(node); + if (signature != null) { + returnType = context.checker.getReturnTypeOfSignature(signature); } + + const {description: returnDescription, typeHint: returnTypeHint} = getReturnFromJsDoc(jsDoc); + + yield { + kind: 'function', + name: node.name.getText(), + description: jsDoc?.description, + summary: getSummaryFromJsDoc(jsDoc), + parameters, + return: { + type: typeToSchemaType(context, returnTypeHint || returnType), + description: returnDescription + } + }; } } } /** - * Returns fields from a declaration + * Computes the class fields for a given component declaration * @param declaration * @param context */ -function* getClassFieldsForDeclaration(declaration: ComponentDeclaration, context: TransformerContext): IterableIterator { - const visibility = context.config.visibility ?? "public"; +function* getClassFieldsForComponent( + declaration: ComponentDeclaration, + context: TransformerContext +): IterableIterator { + const visibility = context.config.visibility ?? 'public'; for (const member of declaration.members) { if (member.visibility === visibility && member.propName != null) { yield { - kind: "field", + kind: 'field', name: member.propName, privacy: member.visibility, description: member.jsDoc?.description, @@ -122,25 +143,35 @@ function* getClassFieldsForDeclaration(declaration: ComponentDeclaration, contex } /** - * Returns method docs for a declaration + * Computes the methods for a given component declaration * @param declaration * @param context */ -function* getMethodsForDeclaration(declaration: ComponentDeclaration, context: TransformerContext): IterableIterator { - const visibility = context.config.visibility ?? "public"; +function* getMethodsForComponent( + declaration: ComponentDeclaration, + context: TransformerContext +): IterableIterator { + const visibility = context.config.visibility ?? 'public'; for (const method of declaration.methods) { const parameters: schema.Parameter[] = []; const node = method.node; let returnType: tsModule.Type | undefined = undefined; - if (method.visibility === visibility && node !== undefined && tsModule.isMethodDeclaration(node)) { + if ( + method.visibility === visibility && + node !== undefined && + tsModule.isMethodDeclaration(node) + ) { for (const param of node.parameters) { const name = param.name.getText(); - const { description, typeHint } = getParameterFromJsDoc(name, method.jsDoc); + const {description, typeHint} = getParameterFromJsDoc(name, method.jsDoc); parameters.push({ name: name, - type: typeToSchemaType(context, typeHint || (param.type != null ? context.checker.getTypeAtLocation(param.type) : undefined)), + type: typeToSchemaType( + context, + typeHint || (param.type != null ? context.checker.getTypeAtLocation(param.type) : undefined) + ), description: description, optional: param.questionToken !== undefined }); @@ -154,10 +185,12 @@ function* getMethodsForDeclaration(declaration: ComponentDeclaration, context: T } // Get return info from jsdoc - const { description: returnDescription, typeHint: returnTypeHint } = getReturnFromJsDoc(method.jsDoc); + const {description: returnDescription, typeHint: returnTypeHint} = getReturnFromJsDoc( + method.jsDoc + ); yield { - kind: "method", + kind: 'method', name: method.name, privacy: method.visibility, description: method.jsDoc?.description, @@ -174,58 +207,99 @@ function* getMethodsForDeclaration(declaration: ComponentDeclaration, context: T } /** - * Returns class member docs for a declaration + * Computes the class members for a given component declaration * @param declaration * @param context */ -function getClassMembersForDeclaration(declaration: ComponentDeclaration, context: TransformerContext): schema.ClassMember[] { - return [...getClassFieldsForDeclaration(declaration, context), ...getMethodsForDeclaration(declaration, context)]; +function* getClassMembersForDeclaration( + declaration: ComponentDeclaration, + context: TransformerContext +): IterableIterator { + yield* getClassFieldsForComponent(declaration, context); + yield* getMethodsForComponent(declaration, context); } -function* getEventsFromDeclaration(declaration: ComponentDeclaration, context: TransformerContext): IterableIterator { - const visibility = context.config.visibility ?? "public"; +/** + * Computes the events for a given component declaration + * @param declaration + * @param context + */ +function* getEventsFromComponent( + declaration: ComponentDeclaration, + context: TransformerContext +): IterableIterator { + const visibility = context.config.visibility ?? 'public'; for (const event of declaration.events) { if (event.visibility === visibility) { - const type = event.type?.() ?? { kind: "ANY" }; + const type = event.type?.() ?? {kind: 'ANY'}; const simpleType = isSimpleType(type) ? type : toSimpleType(type, context.checker); - const typeName = simpleType.kind === "GENERIC_ARGUMENTS" ? simpleType.target.name : simpleType.name; + const typeName = + simpleType.kind === 'GENERIC_ARGUMENTS' ? simpleType.target.name : simpleType.name; yield { description: event.jsDoc?.description, name: event.name, inheritedFrom: getInheritedFromReference(declaration, event, context), - type: typeName === null || typeName === undefined || simpleType.kind === "ANY" ? { text: "Event" } : { text: typeName } + type: + typeName === null || typeName === undefined || simpleType.kind === 'ANY' + ? {text: 'Event'} + : {text: typeName} }; } } } -function* getSlotsFromDeclaration(declaration: ComponentDeclaration, context: TransformerContext): IterableIterator { +/** + * Computes the slots for a given component declaration + * @param declaration + * @param context + */ +function* getSlotsFromComponent( + declaration: ComponentDeclaration, + context: TransformerContext +): IterableIterator { for (const slot of declaration.slots) { yield { description: slot.jsDoc?.description, - name: slot.name ?? "" + name: slot.name ?? '' }; } } -function* getAttributesFromDeclaration(declaration: ComponentDeclaration, context: TransformerContext): IterableIterator { - const visibility = context.config.visibility ?? "public"; +/** + * Computes the attributes for a given component declaration + * @param declaration + * @param context + */ +function* getAttributesFromComponent( + declaration: ComponentDeclaration, + context: TransformerContext +): IterableIterator { + const visibility = context.config.visibility ?? 'public'; for (const member of declaration.members) { if (member.visibility === visibility && member.attrName) { - const type = getTypeHintFromType(member.typeHint ?? member.type?.(), context.checker, context.config); + const type = getTypeHintFromType( + member.typeHint ?? member.type?.(), + context.checker, + context.config + ); yield { name: member.attrName, fieldName: member.propName, default: member.default != null ? JSON.stringify(member.default) : undefined, description: member.jsDoc?.description, - type: type === undefined ? undefined : { text: type }, + type: type === undefined ? undefined : {text: type}, inheritedFrom: getInheritedFromReference(declaration, member, context) }; } } } -function* getCSSPropertiesFromDeclaration( +/** + * Computes the CSS properties for a given component declaration + * @param declaration + * @param context + */ +function* getCSSPropertiesFromComponent( declaration: ComponentDeclaration, context: TransformerContext ): IterableIterator { @@ -239,7 +313,15 @@ function* getCSSPropertiesFromDeclaration( } } -function* getCSSPartsFromDeclaration(declaration: ComponentDeclaration, context: TransformerContext): IterableIterator { +/** + * Computes the CSS parts (::part) for a given component declaration + * @param declaration + * @param context + */ +function* getCSSPartsFromComponent( + declaration: ComponentDeclaration, + context: TransformerContext +): IterableIterator { for (const cssPart of declaration.cssParts) { yield { name: cssPart.name, @@ -248,29 +330,41 @@ function* getCSSPartsFromDeclaration(declaration: ComponentDeclaration, context: } } -function getDeclarationForComponentDeclaration( +/** + * Computes the manifest declaration of a given component declaration + * @param declaration + * @param result + * @param context + */ +function getDeclarationForComponent( declaration: ComponentDeclaration, result: AnalyzerResult, context: TransformerContext ): schema.Declaration | undefined { - if (declaration.kind === "interface") { + if (declaration.kind === 'interface') { return undefined; } const superClassClause = getSuperclassHeritageClause(declaration); - const superClass = superClassClause ? getReferenceFromHeritageClause(superClassClause, context) : undefined; - const definition = result.componentDefinitions.find(def => def.declaration?.node === declaration.node); + const superClass = superClassClause + ? getReferenceFromHeritageClause(superClassClause, context) + : undefined; + const definition = result.componentDefinitions.find( + (def) => def.declaration?.node === declaration.node + ); const mixinClauses = getMixinHeritageClauses(declaration); - const mixins = mixinClauses.map(c => getReferenceFromHeritageClause(c, context)).filter((c): c is schema.Reference => c !== undefined); - const members = getClassMembersForDeclaration(declaration, context); - const name = declaration.symbol?.name ?? getNodeName(declaration.node, { ts: tsModule }); + const mixins = mixinClauses + .map((c) => getReferenceFromHeritageClause(c, context)) + .filter((c): c is schema.Reference => c !== undefined); + const members = [...getClassMembersForDeclaration(declaration, context)]; + const name = declaration.symbol?.name ?? getNodeName(declaration.node, {ts: tsModule}); if (!name) { return undefined; } const classDecl: schema.ClassDeclaration = { - kind: "class", + kind: 'class', name, superclass: superClass, mixins: mixins.length > 0 ? mixins : undefined, @@ -283,11 +377,11 @@ function getDeclarationForComponentDeclaration( return classDecl; } - const events = [...getEventsFromDeclaration(declaration, context)]; - const slots = [...getSlotsFromDeclaration(declaration, context)]; - const attributes = [...getAttributesFromDeclaration(declaration, context)]; - const cssProperties = [...getCSSPropertiesFromDeclaration(declaration, context)]; - const cssParts = [...getCSSPartsFromDeclaration(declaration, context)]; + const events = [...getEventsFromComponent(declaration, context)]; + const slots = [...getSlotsFromComponent(declaration, context)]; + const attributes = [...getAttributesFromComponent(declaration, context)]; + const cssProperties = [...getCSSPropertiesFromComponent(declaration, context)]; + const cssParts = [...getCSSPartsFromComponent(declaration, context)]; // Return a custom element doc if a definition was found const customElementDoc: schema.CustomElementDeclaration = { @@ -304,10 +398,18 @@ function getDeclarationForComponentDeclaration( return customElementDoc; } -function* getComponentDeclarationsFromResult(result: AnalyzerResult, context: TransformerContext): IterableIterator { +/** + * Computes the manifest declarations for a given analyzer result + * @param result + * @param context + */ +function* getComponentDeclarationsFromResult( + result: AnalyzerResult, + context: TransformerContext +): IterableIterator { if (result.declarations) { for (const decl of result.declarations) { - const schemaDecl = getDeclarationForComponentDeclaration(decl, result, context); + const schemaDecl = getDeclarationForComponent(decl, result, context); if (schemaDecl) { yield schemaDecl; } @@ -316,7 +418,7 @@ function* getComponentDeclarationsFromResult(result: AnalyzerResult, context: Tr for (const definition of result.componentDefinitions) { if (definition.declaration) { - const schemaDecl = getDeclarationForComponentDeclaration(definition.declaration, result, context); + const schemaDecl = getDeclarationForComponent(definition.declaration, result, context); if (schemaDecl) { yield schemaDecl; } @@ -329,6 +431,10 @@ function* getComponentDeclarationsFromResult(result: AnalyzerResult, context: Tr * @param result * @param context */ -export function getDeclarationsFromResult(result: AnalyzerResult, context: TransformerContext): schema.Declaration[] { - return [...getExportedDeclarationsFromResult(result, context), ...getComponentDeclarationsFromResult(result, context)]; +export function* getDeclarationsFromResult( + result: AnalyzerResult, + context: TransformerContext +): IterableIterator { + yield* getExportedDeclarationsFromResult(result, context); + yield* getComponentDeclarationsFromResult(result, context); } diff --git a/src/transformers/custom-elements-manifest/get-exports.ts b/src/transformers/custom-elements-manifest/get-exports.ts index 45b3760e..5d173c91 100644 --- a/src/transformers/custom-elements-manifest/get-exports.ts +++ b/src/transformers/custom-elements-manifest/get-exports.ts @@ -1,9 +1,12 @@ -import { AnalyzerResult } from "../../analyze/types/analyzer-result"; -import { TransformerContext } from "../transformer-context"; -import * as schema from "./schema"; -import { getReferenceForNode } from "./utils"; +import {AnalyzerResult} from '../../analyze/types/analyzer-result'; +import {TransformerContext} from '../transformer-context'; +import * as schema from './schema'; +import {getReferenceForNode} from './utils'; -function* getCustomElementExportsFromResult(result: AnalyzerResult, context: TransformerContext): IterableIterator { +function* getCustomElementExportsFromResult( + result: AnalyzerResult, + context: TransformerContext +): IterableIterator { for (const definition of result.componentDefinitions) { // It's not possible right now to model a tag name where the // declaration couldn't be resolved because the "declaration" is required @@ -12,14 +15,17 @@ function* getCustomElementExportsFromResult(result: AnalyzerResult, context: Tra } yield { - kind: "custom-element-definition", + kind: 'custom-element-definition', name: definition.tagName, declaration: getReferenceForNode(definition.declaration.node, context) }; } } -function* getExportedNamesFromResult(result: AnalyzerResult, context: TransformerContext): IterableIterator { +function* getExportedNamesFromResult( + result: AnalyzerResult, + context: TransformerContext +): IterableIterator { const symbol = context.checker.getSymbolAtLocation(result.sourceFile); if (symbol == null) { return; @@ -29,7 +35,7 @@ function* getExportedNamesFromResult(result: AnalyzerResult, context: Transforme for (const exp of exports) { yield { - kind: "js", + kind: 'js', name: exp.name, declaration: getReferenceForNode(exp.valueDeclaration, context) }; @@ -41,6 +47,10 @@ function* getExportedNamesFromResult(result: AnalyzerResult, context: Transforme * @param result * @param context */ -export function getExportsFromResult(result: AnalyzerResult, context: TransformerContext): schema.Export[] { - return [...getCustomElementExportsFromResult(result, context), ...getExportedNamesFromResult(result, context)]; +export function* getExportsFromResult( + result: AnalyzerResult, + context: TransformerContext +): IterableIterator { + yield* getCustomElementExportsFromResult(result, context); + yield* getExportedNamesFromResult(result, context); } diff --git a/src/transformers/custom-elements-manifest/schema.ts b/src/transformers/custom-elements-manifest/schema.ts deleted file mode 100644 index f109ffb7..00000000 --- a/src/transformers/custom-elements-manifest/schema.ts +++ /dev/null @@ -1,617 +0,0 @@ -/** - * This file comes from the following PR with a proposed JSON schema: - * https://github.com/webcomponents/custom-elements-manifest - */ - -/** - * @license - * Copyright (c) 2019 The Polymer Project Authors. All rights reserved. - * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt - * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt - * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt - * Code distributed by Google as part of the polymer project is also - * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt - */ - -/** - * The top-level interface of a custom elements manifest file. - * - * Because custom elements are JavaScript classes, describing a custom element - * may require describing arbitrary JavaScript concepts like modules, classes, - * functions, etc. So custom elements manifests are capable of documenting - * the elements in a package, as well as those JavaScript concepts. - * - * The modules described in a package should be the public entrypoints that - * other packages may import from. Multiple modules may export the same object - * via re-exports, but in most cases a package should document the single - * canonical export that should be used. - */ -export interface Package { - /** - * The version of the schema used in this file. - */ - schemaVersion: string; - - /** - * The Markdown to use for the main readme of this package. - * - * This can be used to override the readme used by Github or npm if that - * file contains information irrelevant to custom element catalogs and - * documentation viewers. - */ - readme?: string; - - /** - * An array of the modules this package contains. - */ - modules: Array; -} - -// This type may expand in the future to include JSON, CSS, or HTML -// modules. -export type Module = JavaScriptModule; - -export interface JavaScriptModule { - kind: "javascript-module"; - - path: string; - - /** - * A markdown summary suitable for display in a listing. - */ - summary?: string; - - /** - * A markdown description of the module. - */ - description?: string; - - /** - * The declarations of a module. - * - * For documentation purposes, all declarations that are reachable from - * exports should be described here. Ie, functions and objects that may be - * properties of exported objects, or passed as arguments to functions. - */ - declarations?: Array; - - /** - * The exports of a module. This includes JavaScript exports and - * custom element definitions. - */ - exports?: Array; -} - -export type Export = JavaScriptExport | CustomElementExport; - -export interface JavaScriptExport { - kind: "js"; - - /** - * The name of the exported symbol. - * - * JavaScript has a number of ways to export objects which determine the - * correct name to use. - * - * - Default exports must use the name "default". - * - Named exports use the name that is exported. If the export is renamed - * with the "as" clause, use the exported name. - * - Aggregating exports (`* from`) should use the name `*` - */ - name: string; - - /** - * A reference to the exported declaration. - * - * In the case of aggregating exports, the reference's `module` field must be - * defined and the `name` field must be `"*"`. - */ - declaration: Reference; -} - -/** - * A global custom element defintion, ie the result of a - * `customElements.define()` call. - * - * This is represented as an export because a definition makes the element - * available outside of the module it's defined it. - */ -export interface CustomElementExport { - kind: "custom-element-definition"; - - /** - * The tag name of the custom element. - */ - name: string; - - /** - * A reference to the class or other declaration that implements the - * custom element. - */ - declaration: Reference; -} - -export type Declaration = - | ClassDeclaration - | FunctionDeclaration - | MixinDeclaration - | VariableDeclaration - | CustomElementDeclaration - | CustomElementMixinDeclaration; - -/** - * A reference to an export of a module. - * - * All references are required to be publically accessible, so the canonical - * representation of a reference is the export it's available from. - * - * `package` should generally refer to an npm package name. If `package` is - * undefined then the reference is local to this package. If `module` is - * undefined the reference is local to the containing module. - * - * References to global symbols like `Array`, `HTMLElement`, or `Event` should - * use a `package` name of `"global:"`. - */ -export interface Reference { - name: string; - package?: string; - module?: string; -} - -/** - * A reference to the source of a declaration or member. - */ -export interface SourceReference { - /** - * An absolute URL to the source (ie. a GitHub URL). - */ - href: string; -} - -/** - * A description of a custom element class. - * - * Custom elements are JavaScript classes, so this extends from - * `ClassDeclaration` and adds custom-element-specific features like - * attributes, events, and slots. - * - * Note that `tagName` in this interface is optional. Tag names are not - * neccessarily part of a custom element class, but belong to the definition - * (often called the "registration") or the `customElements.define()` call. - * - * Because classes and tag names can only be registered once, there's a - * one-to-one relationship between classes and tag names. For ease of use, - * we allow the tag name here. - * - * Some packages define and register custom elements in separate modules. In - * these cases one `Module` should contain the `CustomElement` without a - * tagName, and another `Module` should contain the - * `CustomElementExport`. - */ -export interface CustomElementDeclaration extends ClassDeclaration, CustomElement {} - -/** - * The additional fields that a custom element adds to classes and mixins. - */ -export interface CustomElement extends ClassLike { - /** - * An optional tag name that should be specified if this is a - * self-registering element. - * - * Self-registering elements must also include a CustomElementExport - * in the module's exports. - */ - tagName?: string; - - /** - * The attributes that this element is known to understand. - */ - attributes?: Attribute[]; - - /** - * The events that this element fires. - */ - events?: Event[]; - - /** - * The shadow dom content slots that this element accepts. - */ - slots?: Slot[]; - - cssParts?: CssPart[]; - - cssProperties?: CssCustomProperty[]; - - demos?: Demo[]; - - /** - * Distinguishes a regular JavaScript class from a - * custom element class - */ - customElement: true; -} - -export interface Attribute { - name: string; - - /** - * A markdown summary suitable for display in a listing. - */ - summary?: string; - - /** - * A markdown description. - */ - description?: string; - - inheritedFrom?: Reference; - - /** - * The type that the attribute will be serialized/deserialized as. - */ - type?: Type; - - /** - * The default value of the attribute, if any. - * - * As attributes are always strings, this is the actual value, not a human - * readable description. - */ - default?: string; - - /** - * The name of the field this attribute is associated with, if any. - */ - fieldName?: string; -} - -export interface Event { - name: string; - - /** - * A markdown summary suitable for display in a listing. - */ - summary?: string; - - /** - * A markdown description. - */ - description?: string; - - /** - * The type of the event object that's fired. - */ - type: Type; - - inheritedFrom?: Reference; -} - -export interface Slot { - /** - * The slot name, or the empty string for an unnamed slot. - */ - name: string; - - /** - * A markdown summary suitable for display in a listing. - */ - summary?: string; - - /** - * A markdown description. - */ - description?: string; -} - -/** - * The description of a CSS Part - */ -export interface CssPart { - name: string; - - /** - * A markdown summary suitable for display in a listing. - */ - summary?: string; - - /** - * A markdown description. - */ - description?: string; -} - -export interface CssCustomProperty { - /** - * The name of the property, including leading `--`. - */ - name: string; - - /** - * The expected syntax of the defined property. Defaults to "*". - * - * The syntax must be a valid CSS [syntax string](https://developer.mozilla.org/en-US/docs/Web/CSS/@property/syntax) - * as defined in the CSS Properties and Values API. - * - * Examples: - * - * "": accepts a color - * " | ": accepts lengths or percentages but not calc expressions with a combination of the two - * "small | medium | large": accepts one of these values set as custom idents. - * "*": any valid token - */ - syntax?: string; - - default?: string; - - /** - * A markdown summary suitable for display in a listing. - */ - summary?: string; - - /** - * A markdown description. - */ - description?: string; -} - -export interface Type { - /** - * The full string representation of the type, in whatever type syntax is - * used, such as JSDoc, Closure, or TypeScript. - */ - text: string; - - /** - * An array of references to the types in the type string. - * - * These references have optional indices into the type string so that tools - * can understand the references in the type string independently of the type - * system and syntax. For example, a documentation viewer could display the - * type `Array` with cross-references to `FooElement` - * and `BarElement` without understanding arrays, generics, or union types. - */ - references?: TypeReference[]; - - source?: SourceReference; -} - -/** - * A reference that is associated with a type string and optionally a range - * within the string. - * - * Start and end must both be present or not present. If they're present, they - * are indices into the associated type string. If they are missing, the entire - * type string is the symbol referenced and the name should match the type - * string. - */ -export interface TypeReference extends Reference { - start?: number; - end?: number; -} - -/** - * The common interface of classes and mixins. - */ -export interface ClassLike { - name: string; - - /** - * A markdown summary suitable for display in a listing. - */ - summary?: string; - - /** - * A markdown description of the class. - */ - description?: string; - - /** - * The superclass of this class. - * - * If this class is defined with mixin - * applications, the prototype chain includes the mixin applications - * and the true superclass is computed from them. - */ - superclass?: Reference; - - /** - * Any class mixins applied in the extends clause of this class. - * - * If mixins are applied in the class definition, then the true superclass - * of this class is the result of applying mixins in order to the superclass. - * - * Mixins must be listed in order of their application to the superclass or - * previous mixin application. This means that the innermost mixin is listed - * first. This may read backwards from the common order in JavaScript, but - * matches the order of language used to describe mixin application, like - * "S with A, B". - * - * @example - * - * ```javascript - * class T extends B(A(S)) {} - * ``` - * - * is described by: - * ```json - * { - * "kind": "class", - * "superclass": { - * "name": "S" - * }, - * "mixins": [ - * { - * "name": "A" - * }, - * { - * "name": "B" - * }, - * ] - * } - * ``` - */ - mixins?: Array; - members?: Array; - - source?: SourceReference; -} - -export interface ClassDeclaration extends ClassLike { - kind: "class"; -} - -export type ClassMember = ClassField | ClassMethod; - -/** - * The common interface of variables, class fields, and function - * parameters. - */ -export interface PropertyLike { - name: string; - - /** - * A markdown summary suitable for display in a listing. - */ - summary?: string; - - /** - * A markdown description of the field. - */ - description?: string; - - type?: Type; - - default?: string; -} - -export interface ClassField extends PropertyLike { - kind: "field"; - static?: boolean; - privacy?: Privacy; - inheritedFrom?: Reference; - source?: SourceReference; -} - -export interface ClassMethod extends FunctionLike { - kind: "method"; - static?: boolean; - privacy?: Privacy; - inheritedFrom?: Reference; - source?: SourceReference; -} - -/** - * A description of a class mixin. - * - * Mixins are functions which generate a new subclass of a given superclass. - * This interfaces describes the class and custom element features that - * are added by the mixin. As such, it extends the CustomElement interface and - * ClassLike interface. - * - * Since mixins are functions, it also extends the FunctionLike interface. This - * means a mixin is callable, and has parameters and a return type. - * - * The return type is often hard or impossible to accurately describe in type - * systems like TypeScript. It requires generics and an `extends` operator - * that TypeScript lacks. Therefore it's recommended that the return type is - * left empty. The most common form of a mixin function takes a single - * argument, so consumers of this interface should assume that the return type - * is the single argument subclassed by this declaration. - * - * A mixin should not have a superclass. If a mixins composes other mixins, - * they should be listed in the `mixins` field. - * - * See [this article]{@link https://justinfagnani.com/2015/12/21/real-mixins-with-javascript-classes/} - * for more information on the classmixin pattern in JavaScript. - * - * @example - * - * This JavaScript mixin declaration: - * ```javascript - * const MyMixin = (base) => class extends base { - * foo() { ... } - * } - * ``` - * - * Is described by this JSON: - * ```json - * { - * "kind": "mixin", - * "name": "MyMixin", - * "parameters": [ - * { - * "name": "base", - * } - * ], - * "members": [ - * { - * "kind": "method", - * "name": "foo", - * } - * ] - * } - * ``` - */ -export interface MixinDeclaration extends ClassLike, FunctionLike { - kind: "mixin"; -} - -/** - * A class mixin that also adds custom element related properties. - */ -export interface CustomElementMixinDeclaration extends MixinDeclaration, CustomElement {} - -export interface VariableDeclaration extends PropertyLike { - kind: "variable"; - source?: SourceReference; -} - -export interface FunctionDeclaration extends FunctionLike { - kind: "function"; - source?: SourceReference; -} - -export interface Parameter extends PropertyLike { - /** - * Whether the parameter is optional. Undefined implies non-optional. - */ - optional?: boolean; -} - -export interface FunctionLike { - name: string; - - /** - * A markdown summary suitable for display in a listing. - */ - summary?: string; - - /** - * A markdown description. - */ - description?: string; - - parameters?: Parameter[]; - - return?: { - type?: Type; - description?: string; - }; -} - -export type Privacy = "public" | "private" | "protected"; - -export interface Demo { - /** - * A markdown description of the demo. - */ - description?: string; - - /** - * Relative URL of the demo if it's published with the package. Absolute URL - * if it's hosted. - */ - url: string; - - source?: SourceReference; -} diff --git a/src/transformers/custom-elements-manifest/transformer.ts b/src/transformers/custom-elements-manifest/transformer.ts index 7818f942..c7387117 100644 --- a/src/transformers/custom-elements-manifest/transformer.ts +++ b/src/transformers/custom-elements-manifest/transformer.ts @@ -1,15 +1,15 @@ -import * as tsModule from "typescript"; -import { Program, SourceFile } from "typescript"; -import { AnalyzerResult } from "../../analyze/types/analyzer-result"; -import { ComponentDeclaration } from "../../analyze/types/component-declaration"; -import { visitAllHeritageClauses } from "../../analyze/util/component-declaration-util"; -import { TransformerConfig } from "../transformer-config"; -import { TransformerFunction } from "../transformer-function"; -import * as schema from "./schema"; -import { TransformerContext } from "../transformer-context"; -import { getExportsFromResult } from "./get-exports"; -import { getDeclarationsFromResult } from "./get-declarations"; -import { getRelativePath } from "./utils"; +import * as tsModule from 'typescript'; +import {Program, SourceFile} from 'typescript'; +import {AnalyzerResult} from '../../analyze/types/analyzer-result'; +import {ComponentDeclaration} from '../../analyze/types/component-declaration'; +import {visitAllHeritageClauses} from '../../analyze/util/component-declaration-util'; +import {TransformerConfig} from '../transformer-config'; +import {TransformerFunction} from '../transformer-function'; +import * as schema from './schema'; +import {TransformerContext} from '../transformer-context'; +import {getExportsFromResult} from './get-exports'; +import {getDeclarationsFromResult} from './get-declarations'; +import {getRelativePath} from './utils'; /** * Transforms results to a custom elements manifest @@ -17,7 +17,11 @@ import { getRelativePath } from "./utils"; * @param program * @param config */ -export const transformer: TransformerFunction = (results: AnalyzerResult[], program: Program, config: TransformerConfig): string => { +export const transformer: TransformerFunction = ( + results: AnalyzerResult[], + program: Program, + config: TransformerConfig +): string => { const context: TransformerContext = { config, checker: program.getTypeChecker(), @@ -29,10 +33,10 @@ export const transformer: TransformerFunction = (results: AnalyzerResult[], prog const flattenedAnalyzerResults = flattenAnalyzerResults(results); // Transform all analyzer results into modules - const modules = flattenedAnalyzerResults.map(result => resultToModule(result, context)); + const modules = flattenedAnalyzerResults.map((result) => resultToModule(result, context)); const manifest: schema.Package = { - schemaVersion: "1.0.0", + schemaVersion: '1.0.0', modules }; @@ -44,12 +48,15 @@ export const transformer: TransformerFunction = (results: AnalyzerResult[], prog * @param result * @param context */ -function resultToModule(result: AnalyzerResult, context: TransformerContext): schema.JavaScriptModule { - const exports = getExportsFromResult(result, context); - const declarations = getDeclarationsFromResult(result, context); +function resultToModule( + result: AnalyzerResult, + context: TransformerContext +): schema.JavaScriptModule { + const exports = [...getExportsFromResult(result, context)]; + const declarations = [...getDeclarationsFromResult(result, context)]; return { - kind: "javascript-module", + kind: 'javascript-module', path: getRelativePath(result.sourceFile.fileName, context), declarations: declarations.length === 0 ? undefined : declarations, exports: exports.length === 0 ? undefined : exports @@ -85,7 +92,7 @@ function flattenAnalyzerResults(results: AnalyzerResult[]): AnalyzerResult[] { // Add all existing declarations to the map addDeclarationToMap(decl); - visitAllHeritageClauses(decl, clause => { + visitAllHeritageClauses(decl, (clause) => { // Flatten all component declarations if (clause.declaration != null) { addDeclarationToMap(clause.declaration); @@ -95,7 +102,7 @@ function flattenAnalyzerResults(results: AnalyzerResult[]): AnalyzerResult[] { } // Return new results with flattened declarations - return results.map(result => { + return results.map((result) => { const declarations = declarationMap.get(result.sourceFile); return { diff --git a/src/transformers/custom-elements-manifest/utils.ts b/src/transformers/custom-elements-manifest/utils.ts index 74572033..986cba95 100644 --- a/src/transformers/custom-elements-manifest/utils.ts +++ b/src/transformers/custom-elements-manifest/utils.ts @@ -1,13 +1,16 @@ -import { basename, relative } from "path"; -import { SimpleType } from "ts-simple-type"; -import { Node, SourceFile, Type } from "typescript"; -import { TransformerContext } from "../transformer-context"; -import { JsDoc } from "../../analyze/types/js-doc"; -import * as schema from "./schema"; -import { getNodeName, resolveDeclarations } from "../../analyze/util/ast-util"; -import { ComponentDeclaration, ComponentHeritageClause } from "../../analyze/types/component-declaration"; -import { ComponentFeatureBase } from "../../analyze/types/features/component-feature"; -import { getTypeHintFromType } from "../../util/get-type-hint-from-type"; +import {basename, relative} from 'path'; +import {SimpleType} from 'ts-simple-type'; +import {Node, SourceFile, Type} from 'typescript'; +import {TransformerContext} from '../transformer-context'; +import {JsDoc} from '../../analyze/types/js-doc'; +import * as schema from 'custom-elements-manifest'; +import {getNodeName, resolveDeclarations} from '../../analyze/util/ast-util'; +import { + ComponentDeclaration, + ComponentHeritageClause +} from '../../analyze/types/component-declaration'; +import {ComponentFeatureBase} from '../../analyze/types/features/component-feature'; +import {getTypeHintFromType} from '../../util/get-type-hint-from-type'; /** * Returns a Reference to a node @@ -20,7 +23,8 @@ export function getReferenceForNode(node: Node, context: TransformerContext): sc // Test if the source file is from a typescript lib // TODO: Find a better way of checking this - const isLib = sourceFile.isDeclarationFile && sourceFile.fileName.match(/typescript\/lib.*\.d\.ts$/) != null; + const isLib = + sourceFile.isDeclarationFile && sourceFile.fileName.match(/typescript\/lib.*\.d\.ts$/) != null; if (isLib) { // Only return the name of the declaration if it's from lib return { @@ -63,7 +67,9 @@ export function getInheritedFromReference( * @param context */ export function getRelativePath(fullPath: string, context: TransformerContext): string { - return context.config.cwd != null ? `./${relative(context.config.cwd, fullPath)}` : basename(fullPath); + return context.config.cwd != null + ? `./${relative(context.config.cwd, fullPath)}` + : basename(fullPath); } /** @@ -88,7 +94,10 @@ export function getPackageName(sourceFile: SourceFile): string | undefined { * @param name * @param jsDoc */ -export function getParameterFromJsDoc(name: string, jsDoc: JsDoc | undefined): { description?: string; typeHint?: string } { +export function getParameterFromJsDoc( + name: string, + jsDoc: JsDoc | undefined +): {description?: string; typeHint?: string} { if (jsDoc?.tags == undefined) { return {}; } @@ -96,8 +105,8 @@ export function getParameterFromJsDoc(name: string, jsDoc: JsDoc | undefined): { for (const tag of jsDoc.tags) { const parsed = tag.parsed(); - if (parsed.tag === "param" && parsed.name === name) { - return { description: parsed.description, typeHint: parsed.type }; + if (parsed.tag === 'param' && parsed.name === name) { + return {description: parsed.description, typeHint: parsed.type}; } } @@ -108,15 +117,17 @@ export function getParameterFromJsDoc(name: string, jsDoc: JsDoc | undefined): { * Get return description and return typeHint from jsdoc * @param jsDoc */ -export function getReturnFromJsDoc(jsDoc: JsDoc | undefined): { description?: string; typeHint?: string } { - const tag = jsDoc?.tags?.find(tag => ["returns", "return"].includes(tag.tag)); +export function getReturnFromJsDoc( + jsDoc: JsDoc | undefined +): {description?: string; typeHint?: string} { + const tag = jsDoc?.tags?.find((tag) => ['returns', 'return'].includes(tag.tag)); if (tag == null) { return {}; } const parsed = tag.parsed(); - return { description: parsed.description, typeHint: parsed.type }; + return {description: parsed.description, typeHint: parsed.type}; } /** @@ -124,7 +135,10 @@ export function getReturnFromJsDoc(jsDoc: JsDoc | undefined): { description?: st * @param context * @param type */ -export function typeToSchemaType(context: TransformerContext, type: string | Type | SimpleType | undefined): schema.Type | undefined { +export function typeToSchemaType( + context: TransformerContext, + type: string | Type | SimpleType | undefined +): schema.Type | undefined { const hint = getTypeHintFromType(type, context.checker, context.config); if (!hint) { @@ -141,7 +155,7 @@ export function typeToSchemaType(context: TransformerContext, type: string | Typ * @param jsDoc */ export function getSummaryFromJsDoc(jsDoc: JsDoc | undefined): string | undefined { - const summaryTag = jsDoc?.tags?.find(tag => tag.tag === "summary"); + const summaryTag = jsDoc?.tags?.find((tag) => tag.tag === 'summary'); if (summaryTag == null) { return undefined; @@ -155,7 +169,10 @@ export function getSummaryFromJsDoc(jsDoc: JsDoc | undefined): string | undefine * @param heritage * @param context */ -export function getReferenceFromHeritageClause(heritage: ComponentHeritageClause, context: TransformerContext): schema.Reference | undefined { +export function getReferenceFromHeritageClause( + heritage: ComponentHeritageClause, + context: TransformerContext +): schema.Reference | undefined { const node = heritage.declaration?.node; const identifier = heritage.identifier; @@ -173,7 +190,7 @@ export function getReferenceFromHeritageClause(heritage: ComponentHeritageClause // Just return the name of the reference if nothing could be resolved const name = getNodeName(identifier, context); if (name != null) { - return { name }; + return {name}; } return undefined; diff --git a/src/transformers/index.ts b/src/transformers/index.ts index b2fa136c..7187f38a 100644 --- a/src/transformers/index.ts +++ b/src/transformers/index.ts @@ -1,3 +1,3 @@ -export * from "./transformer-config"; -export * from "./transformer-kind"; -export * from "./transform-analyzer-result"; +export * from './transformer-config'; +export * from './transformer-kind'; +export * from './transform-analyzer-result'; From 213c5e495df4b577ddde030f313e2dd9f2dc4dae Mon Sep 17 00:00:00 2001 From: 43081j <43081j@users.noreply.github.com> Date: Mon, 19 Jul 2021 22:07:21 +0100 Subject: [PATCH 11/13] add prettier and editorconfig config --- .prettierrc.json | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .prettierrc.json diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 00000000..23100c19 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,10 @@ +{ + "bracketSpacing": false, + "printWidth": 100, + "semi": true, + "singleQuote": false, + "tabWidth": 1, + "trailingComma": "none", + "useTabs": true, + "arrowParens": "always" +} From 9dd31baf2c568901313835880cbc995365c7c4f1 Mon Sep 17 00:00:00 2001 From: 43081j <43081j@users.noreply.github.com> Date: Mon, 19 Jul 2021 22:09:48 +0100 Subject: [PATCH 12/13] put double quotes back --- .../get-declarations.ts | 52 +++++++++---------- .../custom-elements-manifest/get-exports.ts | 12 ++--- .../custom-elements-manifest/transformer.ts | 28 +++++----- .../custom-elements-manifest/utils.ts | 26 +++++----- 4 files changed, 59 insertions(+), 59 deletions(-) diff --git a/src/transformers/custom-elements-manifest/get-declarations.ts b/src/transformers/custom-elements-manifest/get-declarations.ts index e7a0f647..2ed6809c 100644 --- a/src/transformers/custom-elements-manifest/get-declarations.ts +++ b/src/transformers/custom-elements-manifest/get-declarations.ts @@ -8,19 +8,19 @@ * * For example, mapping known exports into a list of manifest declarations. */ -import * as tsModule from 'typescript'; -import {isSimpleType, toSimpleType} from 'ts-simple-type'; -import {ComponentDeclaration} from '../../analyze/types/component-declaration'; +import * as tsModule from "typescript"; +import {isSimpleType, toSimpleType} from "ts-simple-type"; +import {ComponentDeclaration} from "../../analyze/types/component-declaration"; import { getMixinHeritageClauses, getSuperclassHeritageClause -} from '../../analyze/util/component-declaration-util'; -import {findParent, getNodeName} from '../../analyze/util/ast-util'; -import {getJsDoc} from '../../analyze/util/js-doc-util'; -import {getTypeHintFromType} from '../../util/get-type-hint-from-type'; -import {AnalyzerResult} from '../../analyze/types/analyzer-result'; -import * as schema from 'custom-elements-manifest'; -import {TransformerContext} from '../transformer-context'; +} from "../../analyze/util/component-declaration-util"; +import {findParent, getNodeName} from "../../analyze/util/ast-util"; +import {getJsDoc} from "../../analyze/util/js-doc-util"; +import {getTypeHintFromType} from "../../util/get-type-hint-from-type"; +import {AnalyzerResult} from "../../analyze/types/analyzer-result"; +import * as schema from "custom-elements-manifest"; +import {TransformerContext} from "../transformer-context"; import { typeToSchemaType, getSummaryFromJsDoc, @@ -28,7 +28,7 @@ import { getReturnFromJsDoc, getReferenceFromHeritageClause, getInheritedFromReference -} from './utils'; +} from "./utils"; /** * Computes the exported symbols of a result (e.g. variables, functions, etc.) @@ -60,7 +60,7 @@ function* getExportedDeclarationsFromResult( const jsDoc = getJsDoc(variableStatement, tsModule); yield { - kind: 'variable', + kind: "variable", name: node.name.getText(), description: jsDoc?.description, type: typeToSchemaType(context, context.checker.getTypeAtLocation(node)), @@ -101,7 +101,7 @@ function* getExportedDeclarationsFromResult( const {description: returnDescription, typeHint: returnTypeHint} = getReturnFromJsDoc(jsDoc); yield { - kind: 'function', + kind: "function", name: node.name.getText(), description: jsDoc?.description, summary: getSummaryFromJsDoc(jsDoc), @@ -124,11 +124,11 @@ function* getClassFieldsForComponent( declaration: ComponentDeclaration, context: TransformerContext ): IterableIterator { - const visibility = context.config.visibility ?? 'public'; + const visibility = context.config.visibility ?? "public"; for (const member of declaration.members) { if (member.visibility === visibility && member.propName != null) { yield { - kind: 'field', + kind: "field", name: member.propName, privacy: member.visibility, description: member.jsDoc?.description, @@ -151,7 +151,7 @@ function* getMethodsForComponent( declaration: ComponentDeclaration, context: TransformerContext ): IterableIterator { - const visibility = context.config.visibility ?? 'public'; + const visibility = context.config.visibility ?? "public"; for (const method of declaration.methods) { const parameters: schema.Parameter[] = []; const node = method.node; @@ -190,7 +190,7 @@ function* getMethodsForComponent( ); yield { - kind: 'method', + kind: "method", name: method.name, privacy: method.visibility, description: method.jsDoc?.description, @@ -228,20 +228,20 @@ function* getEventsFromComponent( declaration: ComponentDeclaration, context: TransformerContext ): IterableIterator { - const visibility = context.config.visibility ?? 'public'; + const visibility = context.config.visibility ?? "public"; for (const event of declaration.events) { if (event.visibility === visibility) { - const type = event.type?.() ?? {kind: 'ANY'}; + const type = event.type?.() ?? {kind: "ANY"}; const simpleType = isSimpleType(type) ? type : toSimpleType(type, context.checker); const typeName = - simpleType.kind === 'GENERIC_ARGUMENTS' ? simpleType.target.name : simpleType.name; + simpleType.kind === "GENERIC_ARGUMENTS" ? simpleType.target.name : simpleType.name; yield { description: event.jsDoc?.description, name: event.name, inheritedFrom: getInheritedFromReference(declaration, event, context), type: - typeName === null || typeName === undefined || simpleType.kind === 'ANY' - ? {text: 'Event'} + typeName === null || typeName === undefined || simpleType.kind === "ANY" + ? {text: "Event"} : {text: typeName} }; } @@ -260,7 +260,7 @@ function* getSlotsFromComponent( for (const slot of declaration.slots) { yield { description: slot.jsDoc?.description, - name: slot.name ?? '' + name: slot.name ?? "" }; } } @@ -274,7 +274,7 @@ function* getAttributesFromComponent( declaration: ComponentDeclaration, context: TransformerContext ): IterableIterator { - const visibility = context.config.visibility ?? 'public'; + const visibility = context.config.visibility ?? "public"; for (const member of declaration.members) { if (member.visibility === visibility && member.attrName) { const type = getTypeHintFromType( @@ -341,7 +341,7 @@ function getDeclarationForComponent( result: AnalyzerResult, context: TransformerContext ): schema.Declaration | undefined { - if (declaration.kind === 'interface') { + if (declaration.kind === "interface") { return undefined; } @@ -364,7 +364,7 @@ function getDeclarationForComponent( } const classDecl: schema.ClassDeclaration = { - kind: 'class', + kind: "class", name, superclass: superClass, mixins: mixins.length > 0 ? mixins : undefined, diff --git a/src/transformers/custom-elements-manifest/get-exports.ts b/src/transformers/custom-elements-manifest/get-exports.ts index 5d173c91..9cce7e7e 100644 --- a/src/transformers/custom-elements-manifest/get-exports.ts +++ b/src/transformers/custom-elements-manifest/get-exports.ts @@ -1,7 +1,7 @@ -import {AnalyzerResult} from '../../analyze/types/analyzer-result'; -import {TransformerContext} from '../transformer-context'; -import * as schema from './schema'; -import {getReferenceForNode} from './utils'; +import {AnalyzerResult} from "../../analyze/types/analyzer-result"; +import {TransformerContext} from "../transformer-context"; +import * as schema from "./schema"; +import {getReferenceForNode} from "./utils"; function* getCustomElementExportsFromResult( result: AnalyzerResult, @@ -15,7 +15,7 @@ function* getCustomElementExportsFromResult( } yield { - kind: 'custom-element-definition', + kind: "custom-element-definition", name: definition.tagName, declaration: getReferenceForNode(definition.declaration.node, context) }; @@ -35,7 +35,7 @@ function* getExportedNamesFromResult( for (const exp of exports) { yield { - kind: 'js', + kind: "js", name: exp.name, declaration: getReferenceForNode(exp.valueDeclaration, context) }; diff --git a/src/transformers/custom-elements-manifest/transformer.ts b/src/transformers/custom-elements-manifest/transformer.ts index c7387117..6ec86a46 100644 --- a/src/transformers/custom-elements-manifest/transformer.ts +++ b/src/transformers/custom-elements-manifest/transformer.ts @@ -1,15 +1,15 @@ -import * as tsModule from 'typescript'; -import {Program, SourceFile} from 'typescript'; -import {AnalyzerResult} from '../../analyze/types/analyzer-result'; -import {ComponentDeclaration} from '../../analyze/types/component-declaration'; -import {visitAllHeritageClauses} from '../../analyze/util/component-declaration-util'; -import {TransformerConfig} from '../transformer-config'; -import {TransformerFunction} from '../transformer-function'; -import * as schema from './schema'; -import {TransformerContext} from '../transformer-context'; -import {getExportsFromResult} from './get-exports'; -import {getDeclarationsFromResult} from './get-declarations'; -import {getRelativePath} from './utils'; +import * as tsModule from "typescript"; +import {Program, SourceFile} from "typescript"; +import {AnalyzerResult} from "../../analyze/types/analyzer-result"; +import {ComponentDeclaration} from "../../analyze/types/component-declaration"; +import {visitAllHeritageClauses} from "../../analyze/util/component-declaration-util"; +import {TransformerConfig} from "../transformer-config"; +import {TransformerFunction} from "../transformer-function"; +import * as schema from "./schema"; +import {TransformerContext} from "../transformer-context"; +import {getExportsFromResult} from "./get-exports"; +import {getDeclarationsFromResult} from "./get-declarations"; +import {getRelativePath} from "./utils"; /** * Transforms results to a custom elements manifest @@ -36,7 +36,7 @@ export const transformer: TransformerFunction = ( const modules = flattenedAnalyzerResults.map((result) => resultToModule(result, context)); const manifest: schema.Package = { - schemaVersion: '1.0.0', + schemaVersion: "1.0.0", modules }; @@ -56,7 +56,7 @@ function resultToModule( const declarations = [...getDeclarationsFromResult(result, context)]; return { - kind: 'javascript-module', + kind: "javascript-module", path: getRelativePath(result.sourceFile.fileName, context), declarations: declarations.length === 0 ? undefined : declarations, exports: exports.length === 0 ? undefined : exports diff --git a/src/transformers/custom-elements-manifest/utils.ts b/src/transformers/custom-elements-manifest/utils.ts index 986cba95..d40a28b9 100644 --- a/src/transformers/custom-elements-manifest/utils.ts +++ b/src/transformers/custom-elements-manifest/utils.ts @@ -1,16 +1,16 @@ -import {basename, relative} from 'path'; -import {SimpleType} from 'ts-simple-type'; -import {Node, SourceFile, Type} from 'typescript'; -import {TransformerContext} from '../transformer-context'; -import {JsDoc} from '../../analyze/types/js-doc'; -import * as schema from 'custom-elements-manifest'; -import {getNodeName, resolveDeclarations} from '../../analyze/util/ast-util'; +import {basename, relative} from "path"; +import {SimpleType} from "ts-simple-type"; +import {Node, SourceFile, Type} from "typescript"; +import {TransformerContext} from "../transformer-context"; +import {JsDoc} from "../../analyze/types/js-doc"; +import * as schema from "custom-elements-manifest"; +import {getNodeName, resolveDeclarations} from "../../analyze/util/ast-util"; import { ComponentDeclaration, ComponentHeritageClause -} from '../../analyze/types/component-declaration'; -import {ComponentFeatureBase} from '../../analyze/types/features/component-feature'; -import {getTypeHintFromType} from '../../util/get-type-hint-from-type'; +} from "../../analyze/types/component-declaration"; +import {ComponentFeatureBase} from "../../analyze/types/features/component-feature"; +import {getTypeHintFromType} from "../../util/get-type-hint-from-type"; /** * Returns a Reference to a node @@ -105,7 +105,7 @@ export function getParameterFromJsDoc( for (const tag of jsDoc.tags) { const parsed = tag.parsed(); - if (parsed.tag === 'param' && parsed.name === name) { + if (parsed.tag === "param" && parsed.name === name) { return {description: parsed.description, typeHint: parsed.type}; } } @@ -120,7 +120,7 @@ export function getParameterFromJsDoc( export function getReturnFromJsDoc( jsDoc: JsDoc | undefined ): {description?: string; typeHint?: string} { - const tag = jsDoc?.tags?.find((tag) => ['returns', 'return'].includes(tag.tag)); + const tag = jsDoc?.tags?.find((tag) => tag.tag === "returns" || tag.tag === "return"); if (tag == null) { return {}; @@ -155,7 +155,7 @@ export function typeToSchemaType( * @param jsDoc */ export function getSummaryFromJsDoc(jsDoc: JsDoc | undefined): string | undefined { - const summaryTag = jsDoc?.tags?.find((tag) => tag.tag === 'summary'); + const summaryTag = jsDoc?.tags?.find((tag) => tag.tag === "summary"); if (summaryTag == null) { return undefined; From a9e8bcb2ccdf888c325cd2080d26252346efccb8 Mon Sep 17 00:00:00 2001 From: 43081j <43081j@users.noreply.github.com> Date: Sat, 6 Nov 2021 14:16:20 +0000 Subject: [PATCH 13/13] correct some iterable types & import schema directly --- .../get-declarations.ts | 11 ++++++---- .../custom-elements-manifest/get-exports.ts | 16 ++++++++------ .../custom-elements-manifest/transformer.ts | 2 +- .../custom-elements-manifest/utils.ts | 22 +++++++------------ 4 files changed, 25 insertions(+), 26 deletions(-) diff --git a/src/transformers/custom-elements-manifest/get-declarations.ts b/src/transformers/custom-elements-manifest/get-declarations.ts index 2ed6809c..144731c2 100644 --- a/src/transformers/custom-elements-manifest/get-declarations.ts +++ b/src/transformers/custom-elements-manifest/get-declarations.ts @@ -19,7 +19,7 @@ import {findParent, getNodeName} from "../../analyze/util/ast-util"; import {getJsDoc} from "../../analyze/util/js-doc-util"; import {getTypeHintFromType} from "../../util/get-type-hint-from-type"; import {AnalyzerResult} from "../../analyze/types/analyzer-result"; -import * as schema from "custom-elements-manifest"; +import * as schema from "custom-elements-manifest/schema"; import {TransformerContext} from "../transformer-context"; import { typeToSchemaType, @@ -70,6 +70,7 @@ function* getExportedDeclarationsFromResult( if ( exp.flags & tsModule.SymbolFlags.Function && + node && tsModule.isFunctionDeclaration(node) && node.name ) { @@ -214,7 +215,7 @@ function* getMethodsForComponent( function* getClassMembersForDeclaration( declaration: ComponentDeclaration, context: TransformerContext -): IterableIterator { +): IterableIterator { yield* getClassFieldsForComponent(declaration, context); yield* getMethodsForComponent(declaration, context); } @@ -384,7 +385,9 @@ function getDeclarationForComponent( const cssParts = [...getCSSPartsFromComponent(declaration, context)]; // Return a custom element doc if a definition was found - const customElementDoc: schema.CustomElementDeclaration = { + // TODO (43081j): remove the type union once custom-elements-manifest + // has a new version published to NPM + const customElementDoc: schema.CustomElementDeclaration & schema.CustomElement = { ...classDecl, customElement: true, tagName: definition.tagName, @@ -434,7 +437,7 @@ function* getComponentDeclarationsFromResult( export function* getDeclarationsFromResult( result: AnalyzerResult, context: TransformerContext -): IterableIterator { +): IterableIterator { yield* getExportedDeclarationsFromResult(result, context); yield* getComponentDeclarationsFromResult(result, context); } diff --git a/src/transformers/custom-elements-manifest/get-exports.ts b/src/transformers/custom-elements-manifest/get-exports.ts index 9cce7e7e..7c109756 100644 --- a/src/transformers/custom-elements-manifest/get-exports.ts +++ b/src/transformers/custom-elements-manifest/get-exports.ts @@ -1,6 +1,6 @@ import {AnalyzerResult} from "../../analyze/types/analyzer-result"; import {TransformerContext} from "../transformer-context"; -import * as schema from "./schema"; +import * as schema from "custom-elements-manifest/schema"; import {getReferenceForNode} from "./utils"; function* getCustomElementExportsFromResult( @@ -34,11 +34,13 @@ function* getExportedNamesFromResult( const exports = context.checker.getExportsOfModule(symbol); for (const exp of exports) { - yield { - kind: "js", - name: exp.name, - declaration: getReferenceForNode(exp.valueDeclaration, context) - }; + if (exp.valueDeclaration) { + yield { + kind: "js", + name: exp.name, + declaration: getReferenceForNode(exp.valueDeclaration, context) + }; + } } } @@ -50,7 +52,7 @@ function* getExportedNamesFromResult( export function* getExportsFromResult( result: AnalyzerResult, context: TransformerContext -): IterableIterator { +): IterableIterator { yield* getCustomElementExportsFromResult(result, context); yield* getExportedNamesFromResult(result, context); } diff --git a/src/transformers/custom-elements-manifest/transformer.ts b/src/transformers/custom-elements-manifest/transformer.ts index 6ec86a46..b30d4038 100644 --- a/src/transformers/custom-elements-manifest/transformer.ts +++ b/src/transformers/custom-elements-manifest/transformer.ts @@ -5,7 +5,7 @@ import {ComponentDeclaration} from "../../analyze/types/component-declaration"; import {visitAllHeritageClauses} from "../../analyze/util/component-declaration-util"; import {TransformerConfig} from "../transformer-config"; import {TransformerFunction} from "../transformer-function"; -import * as schema from "./schema"; +import * as schema from "custom-elements-manifest/schema"; import {TransformerContext} from "../transformer-context"; import {getExportsFromResult} from "./get-exports"; import {getDeclarationsFromResult} from "./get-declarations"; diff --git a/src/transformers/custom-elements-manifest/utils.ts b/src/transformers/custom-elements-manifest/utils.ts index d40a28b9..df53580d 100644 --- a/src/transformers/custom-elements-manifest/utils.ts +++ b/src/transformers/custom-elements-manifest/utils.ts @@ -3,7 +3,7 @@ import {SimpleType} from "ts-simple-type"; import {Node, SourceFile, Type} from "typescript"; import {TransformerContext} from "../transformer-context"; import {JsDoc} from "../../analyze/types/js-doc"; -import * as schema from "custom-elements-manifest"; +import * as schema from "custom-elements-manifest/schema"; import {getNodeName, resolveDeclarations} from "../../analyze/util/ast-util"; import { ComponentDeclaration, @@ -82,11 +82,7 @@ export function getPackageName(sourceFile: SourceFile): string | undefined { // The following approach is very, very naive and is only temporary. const match = sourceFile.fileName.match(/node_modules\/(.*?)\//); - if (match != null) { - return match[1]; - } - - return undefined; + return match?.[1]; } /** @@ -117,9 +113,10 @@ export function getParameterFromJsDoc( * Get return description and return typeHint from jsdoc * @param jsDoc */ -export function getReturnFromJsDoc( - jsDoc: JsDoc | undefined -): {description?: string; typeHint?: string} { +export function getReturnFromJsDoc(jsDoc: JsDoc | undefined): { + description?: string; + typeHint?: string; +} { const tag = jsDoc?.tags?.find((tag) => tag.tag === "returns" || tag.tag === "return"); if (tag == null) { @@ -146,6 +143,7 @@ export function typeToSchemaType( } return { + // TODO (43081j): specify type references here via the `references` property text: hint }; } @@ -157,11 +155,7 @@ export function typeToSchemaType( export function getSummaryFromJsDoc(jsDoc: JsDoc | undefined): string | undefined { const summaryTag = jsDoc?.tags?.find((tag) => tag.tag === "summary"); - if (summaryTag == null) { - return undefined; - } - - return summaryTag.comment; + return summaryTag?.comment; } /**