From 3e9501b1601114057ded3a9c69182d3eed802c84 Mon Sep 17 00:00:00 2001 From: marco Date: Thu, 11 Nov 2021 00:10:09 +0100 Subject: [PATCH 01/22] initial work on web-types transformer --- .gitignore | 1 + src/cli/analyze/analyze-cli-command.ts | 1 + src/cli/cli.ts | 2 +- src/transformers/transform-analyzer-result.ts | 4 +- src/transformers/transformer-kind.ts | 2 +- src/transformers/webtypes/webtypes-schema.ts | 109 +++++++ .../webtypes/webtypes-transformer.ts | 285 ++++++++++++++++++ 7 files changed, 401 insertions(+), 3 deletions(-) create mode 100644 src/transformers/webtypes/webtypes-schema.ts create mode 100644 src/transformers/webtypes/webtypes-transformer.ts diff --git a/.gitignore b/.gitignore index 52551e30..4e4e95e8 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ node_modules dist lib *.tgz +yarn.lock diff --git a/src/cli/analyze/analyze-cli-command.ts b/src/cli/analyze/analyze-cli-command.ts index 419861a2..eb7f2a11 100644 --- a/src/cli/analyze/analyze-cli-command.ts +++ b/src/cli/analyze/analyze-cli-command.ts @@ -227,6 +227,7 @@ function formatToExtension(kind: TransformerKind): string { switch (kind) { case "json": case "vscode": + case "webtypes": return ".json"; case "md": case "markdown": diff --git a/src/cli/cli.ts b/src/cli/cli.ts index 46635e4f..d2d44634 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", "webtypes"], nargs: 1, alias: "f" }) diff --git a/src/transformers/transform-analyzer-result.ts b/src/transformers/transform-analyzer-result.ts index d2b06b9d..c5a13c91 100644 --- a/src/transformers/transform-analyzer-result.ts +++ b/src/transformers/transform-analyzer-result.ts @@ -8,6 +8,7 @@ import { TransformerConfig } from "./transformer-config"; import { TransformerFunction } from "./transformer-function"; import { TransformerKind } from "./transformer-kind"; import { vscodeTransformer } from "./vscode/vscode-transformer"; +import { webtypesTransformer } from "./webtypes/webtypes-transformer"; const transformerFunctionMap: Record = { debug: debugJsonTransformer, @@ -15,7 +16,8 @@ const transformerFunctionMap: Record = { json2: json2Transformer, markdown: markdownTransformer, md: markdownTransformer, - vscode: vscodeTransformer + vscode: vscodeTransformer, + webtypes: webtypesTransformer }; /** diff --git a/src/transformers/transformer-kind.ts b/src/transformers/transformer-kind.ts index c87a2f3a..22e5a190 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" | "webtypes"; diff --git a/src/transformers/webtypes/webtypes-schema.ts b/src/transformers/webtypes/webtypes-schema.ts new file mode 100644 index 00000000..c864d6bb --- /dev/null +++ b/src/transformers/webtypes/webtypes-schema.ts @@ -0,0 +1,109 @@ +// converted from JSON schema with https://transform.tools/json-schema-to-typescript +// just renamed JSONSchemaForWebTypesPreviewOfVersion20OfTheStandard to WebtypesSchema + +/** + * Language in which JavaScript objects types are specified. + */ +export type JsTypesSyntax = "typescript"; +/** + * Markup language in which descriptions are formatted + */ +export type DescriptionMarkup = "html" | "markdown" | "none"; +/** + * A RegEx pattern to match whole content. Syntax should work with at least ECMA, Java and Python implementations. + */ +export type Pattern = + | string + | { + regex?: string; + "case-sensitive"?: boolean; + [k: string]: unknown; + }; +export type NameConverter = "as-is" | "PascalCase" | "camelCase" | "lowercase" | "UPPERCASE" | "kebab-case" | "snake_case"; +export type NameConverters = NameConverter[]; +/** + * Relative path to icon + */ +export type Icon = string; +export type Html = GenericContributionsHost; +export type GenericContributions = GenericContributionOrProperty[] | GenericContributionOrProperty; +export type GenericContributionOrProperty = string | number | boolean | GenericContribution; +export type GenericContribution = TypedContribution; +export type TypedContribution = BaseContribution; +export type BaseContribution = GenericContributionsHost; +export type Css = GenericContributionsHost; +export type Js = GenericContributionsHost; + +export interface WebtypesSchema { + $schema?: string; + /** + * Framework, for which the components are provided by the library + */ + framework?: string; + /** + * Name of the library + */ + name: string; + /** + * Version of the library, for which web-types are provided + */ + version: string; + "js-types-syntax"?: JsTypesSyntax; + "description-markup"?: DescriptionMarkup; + "framework-config"?: FrameworkConfig; + "default-icon"?: Icon; + contributions?: { + html?: Html; + css?: Css; + js?: Js; + }; +} +export interface FrameworkConfig { + /** + * Specify rules for enabling web framework support. + */ + "enable-when"?: { + /** + * Node.js package names, which enable framework support within the folder containing the package.json. + */ + "node-packages"?: string[]; + /** + * RegExps to match script URLs, which enable framework support within a particular HTML. + */ + "script-url-patterns"?: Pattern[]; + /** + * Extensions of files, which should have the framework support enabled + */ + "file-extensions"?: string[]; + /** + * RegExp patterns to match file names, which should have the framework support enabled + */ + "file-name-patterns"?: Pattern[]; + /** + * Global JavaScript libraries names enabled within the IDE, which enable framework support in the whole project + */ + "ide-libraries"?: string[]; + }; + /** + * Specify rules for disabling web framework support. These rules take precedence over enable-when rules. + */ + "disable-when"?: { + /** + * Extensions of files, which should have the framework support disabled + */ + "file-extensions"?: string[]; + /** + * RegExp patterns to match file names, which should have the framework support disabled + */ + "file-name-patterns"?: Pattern[]; + }; + "canonical-names"?: { + [k: string]: NameConverter; + }; + "name-variants"?: { + [k: string]: NameConverters; + }; +} +export interface GenericContributionsHost { + [k: string]: GenericContributions; +} diff --git a/src/transformers/webtypes/webtypes-transformer.ts b/src/transformers/webtypes/webtypes-transformer.ts new file mode 100644 index 00000000..8b4f9d27 --- /dev/null +++ b/src/transformers/webtypes/webtypes-transformer.ts @@ -0,0 +1,285 @@ +import { getTypeHintFromMethod } from "../../util/get-type-hint-from-method"; +import { getTypeHintFromType } from "../../util/get-type-hint-from-type"; +import { isAssignableToSimpleTypeKind, isSimpleType, SimpleType, toSimpleType, typeToString } from "ts-simple-type"; +import { Program, Type, TypeChecker } from "typescript"; +import { AnalyzerResult } from "../../analyze/types/analyzer-result"; +import { ComponentDefinition } from "../../analyze/types/component-definition"; +import { ComponentEvent } from "../../analyze/types/features/component-event"; +import { ComponentMember } from "../../analyze/types/features/component-member"; +import { JsDoc } from "../../analyze/types/js-doc"; +import { arrayDefined } from "../../util/array-util"; +import { markdownHighlight } from "../markdown/markdown-util"; +import { TransformerConfig } from "../transformer-config"; +import { TransformerFunction } from "../transformer-function"; +import { GenericContributionsHost, WebtypesSchema } from "./webtypes-schema"; + +/** + * Transforms results to json. + * @param results + * @param program + * @param config + */ +export const webtypesTransformer: TransformerFunction = (results: AnalyzerResult[], program: Program, config: TransformerConfig): string => { + const checker = program.getTypeChecker(); + + // Grab all definitions + const definitions = results.map(res => res.componentDefinitions).reduce((acc, cur) => [...acc, ...cur], []); + + // Transform all definitions into "tags" + const elements = definitions.map(d => definitionToHTMLElement(d, checker, config)); + + const webtypesJson: WebtypesSchema = { + $schema: "https://raw.githubusercontent.com/JetBrains/web-types/master/v2-preview/web-types.json", + name: "web-components", // TODO as param + version: "experimental", + //"default-icon": "icons/lit.png", + "js-types-syntax": "typescript", // TODO as param + framework: "lit", + "framework-config": { + "enable-when": { + "node-packages": ["lit"], + "file-extensions": ["ts", "js", "tsx", "jsx"] + } + }, + contributions: { + html: { + elements: elements + /* + attributes: [ + { + "name": "Event listeners", + "description": "Event listeners expression", + "doc-url": "https://lit.dev/docs/templates/expressions/#event-listener-expressions", + "value": { + "kind": "expression", + "type": "(event: Event) => void" + }, + "pattern": { + "items": "/html/events", + "template": [ + "@", + "#item:event name" + ] + } + }, + { + "name": "Boolean Attributes", + "description": "Boolean Attributes expression", + "doc-url": "https://lit.dev/docs/templates/expressions/#boolean-attribute-expressions", + "value": { + "kind": "expression", + "type": "boolean" + }, + "pattern": { + "items": "/html/attributes", + "template": [ + "?", + "#item:attribute name" + ] + } + }, + { + "name": "Properties", + "description": "Properties expression", + "doc-url": "https://lit.dev/docs/templates/expressions/#property-expressions", + "value": { + "kind": "expression", + "type": "any" + }, + "pattern": { + "items": "/html/attributes", + "template": [ + ".", + "#item:property name" + ] + } + } + ] + */ + } + } + }; + + return JSON.stringify(webtypesJson, null, 4); +}; + +function definitionToHTMLElement(definition: ComponentDefinition, checker: TypeChecker, config: TransformerConfig): GenericContributionsHost { + const declaration = definition.declaration; + + if (declaration == null) { + return { + name: definition.tagName, + attributes: [] + }; + } + + /* + // Transform all members into "attributes" + const customElementAttributes = arrayDefined(declaration.members.map(d => componentMemberToVscodeAttr(d, checker))); + const eventAttributes = arrayDefined(declaration.events.map(e => componentEventToVscodeAttr(e, checker))); + + const attributes = [...customElementAttributes, ...eventAttributes]; +*/ + const customElementAttributes = arrayDefined(declaration.members.map(d => componentMemberToAttr(d, checker, config))); + const eventAttributes = arrayDefined(declaration.events.map(e => componentEventToAttr(e, checker, config))); + + const attributes = [...customElementAttributes, ...eventAttributes]; + + return { + name: definition.tagName, + /* + description: formatMetadata(declaration.jsDoc, { + Events: declaration.events.map(e => formatEntryRow(e.name, e.jsDoc, e.type?.(), checker)), + Slots: declaration.slots.map(s => + formatEntryRow(s.name || " ", s.jsDoc, s.permittedTagNames && s.permittedTagNames.map(n => `"${markdownHighlight(n)}"`).join(" | "), checker) + ), + Attributes: declaration.members + .map(m => ("attrName" in m && m.attrName != null ? formatEntryRow(m.attrName, m.jsDoc, m.typeHint || m.type?.(), checker) : undefined)) + .filter(m => m != null), + Properties: declaration.members + .map(m => ("propName" in m && m.propName != null ? formatEntryRow(m.propName, m.jsDoc, m.typeHint || m.type?.(), checker) : undefined)) + .filter(m => m != null) + }), +*/ + attributes: attributes + }; +} + +function componentEventToAttr(event: ComponentEvent, checker: TypeChecker, config: TransformerConfig): GenericContributionsHost | undefined { + return { + name: `@${event.name}` + }; +} + +function componentMemberToAttr(member: ComponentMember, checker: TypeChecker, config: TransformerConfig): GenericContributionsHost | undefined { + if (member.propName == null) { + return undefined; + } + + const isFunction = member.attrName == null; + + let types: string[] | string = getTypeHintFromType(member.typeHint ?? member.type?.(), checker, config)?.split(" | ") ?? []; + if (isFunction) { + types = []; // TODO find a way to support function signatures, types includes signature as string + } + + let name = member.propName; + if (isFunction || types.length == 0 || types.includes("Object")) { + name = "." + name; + } else if (types.length == 1 && types.includes("boolean")) { + name = "?" + name; + } + + if (types.length == 1) { + types = types[0]; + } + + return { + name: name, + value: { + type: types, + required: new Boolean(member.required).valueOf() + //default: member.default // TODO has some strange values + } + /* + description: formatMetadata(formatEntryRow(member.attrName, member.jsDoc, member.typeHint || member.type?.(), checker), { + Property: "propName" in member ? member.propName : undefined, + Default: member.default === undefined ? undefined : String(member.default) + }), + ...((member.type && typeToVscodeValuePart(member.type?.(), checker)) || {}) +*/ + }; +} + +/** + * Converts a type to either a value set or string unions. + * @param type + * @param checker + */ +function typeToVscodeValuePart(type: SimpleType | Type, checker: TypeChecker): { valueSet: "v" } | { values: HtmlDataAttrValue[] } | undefined { + const simpleType = isSimpleType(type) ? type : toSimpleType(type, checker); + + switch (simpleType.kind) { + case "BOOLEAN": + return { valueSet: "v" }; + case "STRING_LITERAL": + return { values: [{ name: simpleType.value }] }; + case "ENUM": + return { values: typesToStringUnion(simpleType.types.map(({ type }) => type)) }; + case "UNION": + return { values: typesToStringUnion(simpleType.types) }; + } + + return undefined; +} + +/** + * Returns a list of strings that represents the types. + * Only looks at literal types and strips the rest. + * @param types + */ +function typesToStringUnion(types: SimpleType[]): HtmlDataAttrValue[] { + return arrayDefined( + types.map(t => { + switch (t.kind) { + case "STRING_LITERAL": + case "NUMBER_LITERAL": + return { name: t.value.toString() }; + default: + return undefined; + } + }) + ); +} + +/** + * Formats description and metadata so that it can be used in documentation. + * @param doc + * @param metadata + */ +function formatMetadata( + doc: string | undefined | JsDoc, + metadata: { [key: string]: string | undefined | (string | undefined)[] } +): string | undefined { + const metaText = arrayDefined( + Object.entries(metadata).map(([key, value]) => { + if (value == null) { + return undefined; + } else if (Array.isArray(value)) { + const filtered = arrayDefined(value); + if (filtered.length === 0) return undefined; + + return `${key}:\n\n${filtered.map(v => ` * ${v}`).join(`\n\n`)}`; + } else { + return `${key}: ${value}`; + } + }) + ).join(`\n\n`); + + const comment = typeof doc === "string" ? doc : doc?.description || ""; + + return `${comment || ""}${metadata ? `${comment ? `\n\n` : ""}${metaText}` : ""}` || undefined; +} + +/** + * Formats name, doc and type so that it can be presented in documentation + * @param name + * @param doc + * @param type + * @param checker + */ +function formatEntryRow(name: string, doc: JsDoc | string | undefined, type: Type | SimpleType | string | undefined, checker: TypeChecker): string { + const comment = typeof doc === "string" ? doc : doc?.description || ""; + const typeText = typeof type === "string" ? type : type == null ? "" : formatType(type, checker); + + return `${markdownHighlight(name)}${typeText == null ? "" : ` {${typeText}}`}${comment == null ? "" : " - "}${comment || ""}`; +} + +/** + * Formats a type to present in documentation + * @param type + * @param checker + */ +function formatType(type: Type | SimpleType, checker: TypeChecker): string | undefined { + return !isAssignableToSimpleTypeKind(type, "ANY", checker) ? markdownHighlight(typeToString(type, checker)) : undefined; +} From 9009b1214ed0c134d15eeb94f06f04a58c252a5c Mon Sep 17 00:00:00 2001 From: jpradelle Date: Thu, 13 Jan 2022 14:11:17 +0100 Subject: [PATCH 02/22] WebTypes in progress --- src/transformers/webtypes/webtypes-schema.ts | 99 +++++- .../webtypes/webtypes-transformer.ts | 282 +++++------------- 2 files changed, 162 insertions(+), 219 deletions(-) diff --git a/src/transformers/webtypes/webtypes-schema.ts b/src/transformers/webtypes/webtypes-schema.ts index c864d6bb..2e5a86ee 100644 --- a/src/transformers/webtypes/webtypes-schema.ts +++ b/src/transformers/webtypes/webtypes-schema.ts @@ -21,18 +21,22 @@ export type Pattern = }; export type NameConverter = "as-is" | "PascalCase" | "camelCase" | "lowercase" | "UPPERCASE" | "kebab-case" | "snake_case"; export type NameConverters = NameConverter[]; + +export type Priority = "lowest" | "low" | "normal" | "high" | "highest"; /** * Relative path to icon */ export type Icon = string; -export type Html = GenericContributionsHost; +// export type Html = GenericContributionsHost; export type GenericContributions = GenericContributionOrProperty[] | GenericContributionOrProperty; -export type GenericContributionOrProperty = string | number | boolean | GenericContribution; +export type GenericContributionOrProperty = string | number | boolean | GenericContribution | Source; export type GenericContribution = TypedContribution; -export type TypedContribution = BaseContribution; -export type BaseContribution = GenericContributionsHost; + +export interface TypedContribution extends BaseContribution { + type?: Type | Type[]; +} + export type Css = GenericContributionsHost; -export type Js = GenericContributionsHost; export interface WebtypesSchema { $schema?: string; @@ -107,3 +111,88 @@ export interface FrameworkConfig { export interface GenericContributionsHost { [k: string]: GenericContributions; } + +export interface SourceFile { + file: string; + offset: number; +} + +export interface SourceModule { + module: string; + symbol: string; +} + +export type Source = SourceFile | SourceModule; + +export type Html = HtmlContributionHost; + +export interface HtmlElement extends BaseContribution, HtmlContributionHost {} + +export interface BaseContribution { + // #/definitions/base-contribution + // [k: string]: GenericContributions; + name?: string; + description?: string; + // "description-sections"?: ; + "doc-url"?: string; + icon?: Icon; + source?: Source; + deprecated?: boolean; + experimental?: boolean; + priority?: Priority; + proximity?: number; + virtual?: boolean; + abstract?: boolean; + extension?: boolean; + extends?: Reference; + // pattern?: NamePatternRoot; + html?: Html; + css?: Css; + js?: Js; + // "exclusive-contributions"?: ; +} + +export interface HtmlContributionHost { + elements?: HtmlElement[]; + attributes?: HtmlAttribute[]; +} + +export interface HtmlAttribute extends BaseContribution, HtmlContributionHost { + value?: HtmlAttributeValue; + default?: string; + required?: boolean; +} + +export interface HtmlAttributeValue { + type?: Type | Type[]; + required?: boolean; + default?: string; + kind?: HtmlAttributeType; +} + +export interface TypeReference { + module?: string; + name: string; +} + +export type Type = string | TypeReference; + +export type HtmlAttributeType = "no-value" | "plain" | "expression"; + +export type Reference = string | ReferenceWithProps; + +export interface ReferenceWithProps { + path: string; + includeVirtual?: boolean; + includeAbstract?: boolean; + filter?: string; +} + +export type Js = JsContributionsHost; + +export interface JsContributionsHost { + events?: GenericJsContribution[]; + properties?: GenericJsContribution[]; +} + +export interface GenericJsContribution extends GenericContribution, JsContributionsHost {} diff --git a/src/transformers/webtypes/webtypes-transformer.ts b/src/transformers/webtypes/webtypes-transformer.ts index 8b4f9d27..41527273 100644 --- a/src/transformers/webtypes/webtypes-transformer.ts +++ b/src/transformers/webtypes/webtypes-transformer.ts @@ -1,17 +1,15 @@ -import { getTypeHintFromMethod } from "../../util/get-type-hint-from-method"; import { getTypeHintFromType } from "../../util/get-type-hint-from-type"; -import { isAssignableToSimpleTypeKind, isSimpleType, SimpleType, toSimpleType, typeToString } from "ts-simple-type"; -import { Program, Type, TypeChecker } from "typescript"; +import { Program, TypeChecker } from "typescript"; import { AnalyzerResult } from "../../analyze/types/analyzer-result"; import { ComponentDefinition } from "../../analyze/types/component-definition"; import { ComponentEvent } from "../../analyze/types/features/component-event"; import { ComponentMember } from "../../analyze/types/features/component-member"; -import { JsDoc } from "../../analyze/types/js-doc"; import { arrayDefined } from "../../util/array-util"; -import { markdownHighlight } from "../markdown/markdown-util"; import { TransformerConfig } from "../transformer-config"; import { TransformerFunction } from "../transformer-function"; -import { GenericContributionsHost, WebtypesSchema } from "./webtypes-schema"; +import { HtmlAttribute, GenericContributionsHost, WebtypesSchema, HtmlElement, BaseContribution, Js } from "./webtypes-schema"; +import { getFirst } from "../../util/set-util"; +import { relative } from "path"; /** * Transforms results to json. @@ -29,73 +27,21 @@ export const webtypesTransformer: TransformerFunction = (results: AnalyzerResult const elements = definitions.map(d => definitionToHTMLElement(d, checker, config)); const webtypesJson: WebtypesSchema = { - $schema: "https://raw.githubusercontent.com/JetBrains/web-types/master/v2-preview/web-types.json", + $schema: "http://json.schemastore.org/web-types", name: "web-components", // TODO as param version: "experimental", //"default-icon": "icons/lit.png", "js-types-syntax": "typescript", // TODO as param - framework: "lit", - "framework-config": { - "enable-when": { - "node-packages": ["lit"], - "file-extensions": ["ts", "js", "tsx", "jsx"] - } - }, + // framework: "lit", + // "framework-config": { + // "enable-when": { + // "node-packages": ["lit"], + // "file-extensions": ["ts", "js", "tsx", "jsx"] + // } + // }, contributions: { html: { elements: elements - /* - attributes: [ - { - "name": "Event listeners", - "description": "Event listeners expression", - "doc-url": "https://lit.dev/docs/templates/expressions/#event-listener-expressions", - "value": { - "kind": "expression", - "type": "(event: Event) => void" - }, - "pattern": { - "items": "/html/events", - "template": [ - "@", - "#item:event name" - ] - } - }, - { - "name": "Boolean Attributes", - "description": "Boolean Attributes expression", - "doc-url": "https://lit.dev/docs/templates/expressions/#boolean-attribute-expressions", - "value": { - "kind": "expression", - "type": "boolean" - }, - "pattern": { - "items": "/html/attributes", - "template": [ - "?", - "#item:attribute name" - ] - } - }, - { - "name": "Properties", - "description": "Properties expression", - "doc-url": "https://lit.dev/docs/templates/expressions/#property-expressions", - "value": { - "kind": "expression", - "type": "any" - }, - "pattern": { - "items": "/html/attributes", - "template": [ - ".", - "#item:property name" - ] - } - } - ] - */ } } }; @@ -103,7 +49,7 @@ export const webtypesTransformer: TransformerFunction = (results: AnalyzerResult return JSON.stringify(webtypesJson, null, 4); }; -function definitionToHTMLElement(definition: ComponentDefinition, checker: TypeChecker, config: TransformerConfig): GenericContributionsHost { +function definitionToHTMLElement(definition: ComponentDefinition, checker: TypeChecker, config: TransformerConfig): HtmlElement { const declaration = definition.declaration; if (declaration == null) { @@ -113,173 +59,81 @@ function definitionToHTMLElement(definition: ComponentDefinition, checker: TypeC }; } - /* - // Transform all members into "attributes" - const customElementAttributes = arrayDefined(declaration.members.map(d => componentMemberToVscodeAttr(d, checker))); - const eventAttributes = arrayDefined(declaration.events.map(e => componentEventToVscodeAttr(e, checker))); + const build: HtmlElement = { + name: definition.tagName + }; + + // Build description + if (declaration?.jsDoc?.description) build.description = declaration.jsDoc.description; + + // Build source section + const node = getFirst(definition.identifierNodes); + const fileName = node?.getSourceFile().fileName; + const path = fileName != null && config.cwd != null ? `./${relative(config.cwd, fileName)}` : undefined; + + if (node?.getText() && path) { + build.source = { + module: path, + symbol: node.getText() + }; + } + + // Build attributes + const customElementAttributes = arrayDefined(declaration.members.map(d => componentMemberToAttr(d.attrName, d, checker, config))); + if (customElementAttributes.length > 0) build.attributes = customElementAttributes; + + const js: Js = {}; - const attributes = [...customElementAttributes, ...eventAttributes]; -*/ - const customElementAttributes = arrayDefined(declaration.members.map(d => componentMemberToAttr(d, checker, config))); + // Build properties + const customElementProperties = arrayDefined(declaration.members.map(d => componentMemberToAttr(d.propName, d, checker, config))); + if (customElementProperties.length > 0) js.properties = customElementProperties; + + // Build events const eventAttributes = arrayDefined(declaration.events.map(e => componentEventToAttr(e, checker, config))); + if (eventAttributes.length > 0) js.events = eventAttributes; - const attributes = [...customElementAttributes, ...eventAttributes]; + if (js.properties || js.events) build.js = js; - return { - name: definition.tagName, - /* - description: formatMetadata(declaration.jsDoc, { - Events: declaration.events.map(e => formatEntryRow(e.name, e.jsDoc, e.type?.(), checker)), - Slots: declaration.slots.map(s => - formatEntryRow(s.name || " ", s.jsDoc, s.permittedTagNames && s.permittedTagNames.map(n => `"${markdownHighlight(n)}"`).join(" | "), checker) - ), - Attributes: declaration.members - .map(m => ("attrName" in m && m.attrName != null ? formatEntryRow(m.attrName, m.jsDoc, m.typeHint || m.type?.(), checker) : undefined)) - .filter(m => m != null), - Properties: declaration.members - .map(m => ("propName" in m && m.propName != null ? formatEntryRow(m.propName, m.jsDoc, m.typeHint || m.type?.(), checker) : undefined)) - .filter(m => m != null) - }), -*/ - attributes: attributes - }; + return build; } function componentEventToAttr(event: ComponentEvent, checker: TypeChecker, config: TransformerConfig): GenericContributionsHost | undefined { return { - name: `@${event.name}` + name: event.name }; } -function componentMemberToAttr(member: ComponentMember, checker: TypeChecker, config: TransformerConfig): GenericContributionsHost | undefined { - if (member.propName == null) { +function componentMemberToAttr( + propName: string | undefined, + member: ComponentMember, + checker: TypeChecker, + config: TransformerConfig +): BaseContribution | undefined { + if (propName == null) { return undefined; } - const isFunction = member.attrName == null; + // const isFunction = member.attrName == null; - let types: string[] | string = getTypeHintFromType(member.typeHint ?? member.type?.(), checker, config)?.split(" | ") ?? []; - if (isFunction) { - types = []; // TODO find a way to support function signatures, types includes signature as string - } - - let name = member.propName; - if (isFunction || types.length == 0 || types.includes("Object")) { - name = "." + name; - } else if (types.length == 1 && types.includes("boolean")) { - name = "?" + name; - } + const types: string[] | string = getTypeHintFromType(member.typeHint ?? member.type?.(), checker, config)?.split(" | ") ?? []; + // if (isFunction) { + // types = []; // TODO find a way to support function signatures, types includes signature as string + // } + // + // if (types.length == 1) { + // types = types[0]; + // } - if (types.length == 1) { - types = types[0]; - } - - return { - name: name, + const attr: HtmlAttribute = { + name: propName, value: { type: types, - required: new Boolean(member.required).valueOf() - //default: member.default // TODO has some strange values + required: new Boolean(member.required).valueOf(), + ...(member.default !== undefined ? { default: JSON.stringify(member.default) } : {}) } - /* - description: formatMetadata(formatEntryRow(member.attrName, member.jsDoc, member.typeHint || member.type?.(), checker), { - Property: "propName" in member ? member.propName : undefined, - Default: member.default === undefined ? undefined : String(member.default) - }), - ...((member.type && typeToVscodeValuePart(member.type?.(), checker)) || {}) -*/ }; -} -/** - * Converts a type to either a value set or string unions. - * @param type - * @param checker - */ -function typeToVscodeValuePart(type: SimpleType | Type, checker: TypeChecker): { valueSet: "v" } | { values: HtmlDataAttrValue[] } | undefined { - const simpleType = isSimpleType(type) ? type : toSimpleType(type, checker); - - switch (simpleType.kind) { - case "BOOLEAN": - return { valueSet: "v" }; - case "STRING_LITERAL": - return { values: [{ name: simpleType.value }] }; - case "ENUM": - return { values: typesToStringUnion(simpleType.types.map(({ type }) => type)) }; - case "UNION": - return { values: typesToStringUnion(simpleType.types) }; - } + if (member?.jsDoc?.description) attr.description = member.jsDoc.description; - return undefined; -} - -/** - * Returns a list of strings that represents the types. - * Only looks at literal types and strips the rest. - * @param types - */ -function typesToStringUnion(types: SimpleType[]): HtmlDataAttrValue[] { - return arrayDefined( - types.map(t => { - switch (t.kind) { - case "STRING_LITERAL": - case "NUMBER_LITERAL": - return { name: t.value.toString() }; - default: - return undefined; - } - }) - ); -} - -/** - * Formats description and metadata so that it can be used in documentation. - * @param doc - * @param metadata - */ -function formatMetadata( - doc: string | undefined | JsDoc, - metadata: { [key: string]: string | undefined | (string | undefined)[] } -): string | undefined { - const metaText = arrayDefined( - Object.entries(metadata).map(([key, value]) => { - if (value == null) { - return undefined; - } else if (Array.isArray(value)) { - const filtered = arrayDefined(value); - if (filtered.length === 0) return undefined; - - return `${key}:\n\n${filtered.map(v => ` * ${v}`).join(`\n\n`)}`; - } else { - return `${key}: ${value}`; - } - }) - ).join(`\n\n`); - - const comment = typeof doc === "string" ? doc : doc?.description || ""; - - return `${comment || ""}${metadata ? `${comment ? `\n\n` : ""}${metaText}` : ""}` || undefined; -} - -/** - * Formats name, doc and type so that it can be presented in documentation - * @param name - * @param doc - * @param type - * @param checker - */ -function formatEntryRow(name: string, doc: JsDoc | string | undefined, type: Type | SimpleType | string | undefined, checker: TypeChecker): string { - const comment = typeof doc === "string" ? doc : doc?.description || ""; - const typeText = typeof type === "string" ? type : type == null ? "" : formatType(type, checker); - - return `${markdownHighlight(name)}${typeText == null ? "" : ` {${typeText}}`}${comment == null ? "" : " - "}${comment || ""}`; -} - -/** - * Formats a type to present in documentation - * @param type - * @param checker - */ -function formatType(type: Type | SimpleType, checker: TypeChecker): string | undefined { - return !isAssignableToSimpleTypeKind(type, "ANY", checker) ? markdownHighlight(typeToString(type, checker)) : undefined; + return attr; } From a73818827cfc9641461c858faecbfce2f6605b17 Mon Sep 17 00:00:00 2001 From: jpradelle Date: Fri, 14 Jan 2022 15:13:00 +0100 Subject: [PATCH 03/22] WebTypes: Add configuration, add CSS properties --- src/cli/analyze/analyze-cli-command.ts | 11 +++- src/cli/analyzer-cli-config.ts | 2 + src/cli/cli.ts | 4 ++ src/transformers/transformer-config.ts | 16 ++++++ src/transformers/webtypes/webtypes-schema.ts | 26 ++++++++- .../webtypes/webtypes-transformer.ts | 56 +++++++++++++------ 6 files changed, 96 insertions(+), 19 deletions(-) diff --git a/src/cli/analyze/analyze-cli-command.ts b/src/cli/analyze/analyze-cli-command.ts index eb7f2a11..df6595cf 100644 --- a/src/cli/analyze/analyze-cli-command.ts +++ b/src/cli/analyze/analyze-cli-command.ts @@ -23,7 +23,7 @@ export const analyzeCliCommand: CliCommand = async (config: AnalyzerCliConfig): const inputGlobs = config.glob || []; // Log warning for experimental json format - if (config.format === "json" || config.format === "json2" || config.outFile?.endsWith(".json")) { + if (config.format === "json" || config.format === "json2" || (config.outFile?.endsWith(".json") && config.format !== "webtypes")) { log( ` !!!!!!!!!!!!! WARNING !!!!!!!!!!!!! @@ -37,6 +37,12 @@ Please follow and contribute to the discussion at: ); } + if (config.format === "webtypes") { + const webTypesConfig = config.webtypesConfig ? JSON.parse(config.webtypesConfig) : null; + if (!webTypesConfig.name) throw makeCliError('Missing webtypes-config "name" property'); + if (!webTypesConfig.version) throw makeCliError('Missing webtypes-config "version" property'); + } + // If no "out" is specified, output to console const outStrategy: OutStrategy = (() => { if (config.outDir == null && config.outFile == null && config.outFiles == null) { @@ -124,6 +130,9 @@ function transformResults(results: AnalyzerResult[] | AnalyzerResult, program: P markdown: config.markdown, cwd: config.cwd }; + if (format == "webtypes") { + transformerConfig.webTypes = config.webtypesConfig ? JSON.parse(config.webtypesConfig) : null; + } return transformAnalyzerResult(format, results, program, transformerConfig); } diff --git a/src/cli/analyzer-cli-config.ts b/src/cli/analyzer-cli-config.ts index 9b8012da..0b2022f9 100644 --- a/src/cli/analyzer-cli-config.ts +++ b/src/cli/analyzer-cli-config.ts @@ -30,4 +30,6 @@ export interface AnalyzerCliConfig { ts?: typeof tsModule; cwd?: string; + + webtypesConfig?: string; } diff --git a/src/cli/cli.ts b/src/cli/cli.ts index d2d44634..992ece93 100644 --- a/src/cli/cli.ts +++ b/src/cli/cli.ts @@ -103,6 +103,10 @@ o {tagname}: The element's tag name`, string: true, hidden: true }) + .option("webtypes-config", { + describe: "WebTypes header configuration", + string: true + }) .alias("v", "version") .help("h") diff --git a/src/transformers/transformer-config.ts b/src/transformers/transformer-config.ts index 00c67e9b..f41dbe04 100644 --- a/src/transformers/transformer-config.ts +++ b/src/transformers/transformer-config.ts @@ -1,4 +1,5 @@ import { VisibilityKind } from "../analyze/types/visibility-kind"; +import { GenericContributions } from "./webtypes/webtypes-schema"; export interface TransformerConfig { cwd?: string; @@ -8,4 +9,19 @@ export interface TransformerConfig { headerLevel?: number; }; inlineTypes?: boolean; + webTypes?: WebTypesTransformerConfig; +} + +export interface WebTypesTransformerConfig { + name: string; + version: string; + "default-icon"?: string; + "js-types-syntax"?: "typescript"; + framework?: string; + "framework-config"?: WebTypesFrameworkConfig; + "description-markup"?: "html" | "markdown" | "none"; +} + +export interface WebTypesFrameworkConfig { + [k: string]: GenericContributions; } diff --git a/src/transformers/webtypes/webtypes-schema.ts b/src/transformers/webtypes/webtypes-schema.ts index 2e5a86ee..8737d046 100644 --- a/src/transformers/webtypes/webtypes-schema.ts +++ b/src/transformers/webtypes/webtypes-schema.ts @@ -36,8 +36,6 @@ export interface TypedContribution extends BaseContribution { type?: Type | Type[]; } -export type Css = GenericContributionsHost; - export interface WebtypesSchema { $schema?: string; /** @@ -196,3 +194,27 @@ export interface JsContributionsHost { } export interface GenericJsContribution extends GenericContribution, JsContributionsHost {} + +export type Css = CssContributionsHost; + +export interface CssContributionsHost { + properties?: CssProperty[]; + "pseudo-elements"?: CssPseudoElement[]; + "pseudo-classes"?: CssPseudoClass[]; + functions?: CssGenericItem[]; + classes?: CssGenericItem[]; +} + +export interface CssProperty extends BaseContribution, CssContributionsHost { + values?: string[]; +} + +export interface CssPseudoElement extends BaseContribution, CssContributionsHost { + arguments?: boolean; +} + +export interface CssPseudoClass extends BaseContribution, CssContributionsHost { + arguments?: boolean; +} + +export interface CssGenericItem extends BaseContribution, CssContributionsHost {} diff --git a/src/transformers/webtypes/webtypes-transformer.ts b/src/transformers/webtypes/webtypes-transformer.ts index 41527273..3516ca16 100644 --- a/src/transformers/webtypes/webtypes-transformer.ts +++ b/src/transformers/webtypes/webtypes-transformer.ts @@ -3,11 +3,12 @@ import { Program, TypeChecker } from "typescript"; import { AnalyzerResult } from "../../analyze/types/analyzer-result"; import { ComponentDefinition } from "../../analyze/types/component-definition"; import { ComponentEvent } from "../../analyze/types/features/component-event"; +import { ComponentCssProperty } from "../../analyze/types/features/component-css-property"; import { ComponentMember } from "../../analyze/types/features/component-member"; import { arrayDefined } from "../../util/array-util"; import { TransformerConfig } from "../transformer-config"; import { TransformerFunction } from "../transformer-function"; -import { HtmlAttribute, GenericContributionsHost, WebtypesSchema, HtmlElement, BaseContribution, Js } from "./webtypes-schema"; +import { HtmlAttribute, WebtypesSchema, HtmlElement, BaseContribution, Js, GenericJsContribution, CssProperty } from "./webtypes-schema"; import { getFirst } from "../../util/set-util"; import { relative } from "path"; @@ -26,19 +27,17 @@ export const webtypesTransformer: TransformerFunction = (results: AnalyzerResult // Transform all definitions into "tags" const elements = definitions.map(d => definitionToHTMLElement(d, checker, config)); + const webTypeConfig = config.webTypes; + const webtypesJson: WebtypesSchema = { $schema: "http://json.schemastore.org/web-types", - name: "web-components", // TODO as param - version: "experimental", - //"default-icon": "icons/lit.png", - "js-types-syntax": "typescript", // TODO as param - // framework: "lit", - // "framework-config": { - // "enable-when": { - // "node-packages": ["lit"], - // "file-extensions": ["ts", "js", "tsx", "jsx"] - // } - // }, + name: webTypeConfig?.name ?? "", + version: webTypeConfig?.version ?? "", + ...(webTypeConfig?.framework ? { name: webTypeConfig?.framework } : {}), + ...(webTypeConfig?.["js-types-syntax"] ? { "js-types-syntax": webTypeConfig?.["js-types-syntax"] } : {}), + ...(webTypeConfig?.["default-icon"] ? { "default-icon": webTypeConfig?.["default-icon"] } : {}), + ...(webTypeConfig?.["framework-config"] ? { "framework-config": webTypeConfig?.["framework-config"] } : {}), + ...(webTypeConfig?.["description-markup"] ? { "description-markup": webTypeConfig?.["description-markup"] } : {}), contributions: { html: { elements: elements @@ -60,7 +59,8 @@ function definitionToHTMLElement(definition: ComponentDefinition, checker: TypeC } const build: HtmlElement = { - name: definition.tagName + name: definition.tagName, + ...(declaration.deprecated !== undefined ? { deprecated: true } : {}) }; // Build description @@ -94,13 +94,36 @@ function definitionToHTMLElement(definition: ComponentDefinition, checker: TypeC if (js.properties || js.events) build.js = js; + // Build css properties + const cssProperties = arrayDefined(declaration.cssProperties.map(e => componentCssPropertiesToAttr(e, checker, config))); + if (cssProperties.length > 0) { + build.css = { + properties: cssProperties + }; + } + return build; } -function componentEventToAttr(event: ComponentEvent, checker: TypeChecker, config: TransformerConfig): GenericContributionsHost | undefined { - return { +function componentEventToAttr(event: ComponentEvent, checker: TypeChecker, config: TransformerConfig): GenericJsContribution { + // console.log(event); + const builtEvent: GenericJsContribution = { name: event.name }; + + if (event?.jsDoc?.description) builtEvent.description = event.jsDoc.description; + + return builtEvent; +} + +function componentCssPropertiesToAttr(cssProperty: ComponentCssProperty, checker: TypeChecker, config: TransformerConfig): CssProperty { + const builtCssProp: CssProperty = { + name: cssProperty.name + }; + + if (cssProperty?.jsDoc?.description) builtCssProp.description = cssProperty.jsDoc.description; + + return builtCssProp; } function componentMemberToAttr( @@ -130,7 +153,8 @@ function componentMemberToAttr( type: types, required: new Boolean(member.required).valueOf(), ...(member.default !== undefined ? { default: JSON.stringify(member.default) } : {}) - } + }, + ...(member.deprecated !== undefined ? { deprecated: true } : {}) }; if (member?.jsDoc?.description) attr.description = member.jsDoc.description; From cf4154607436dc2d9bc6f0b295d0d606dd8d173f Mon Sep 17 00:00:00 2001 From: jpradelle Date: Tue, 18 Jan 2022 15:31:58 +0100 Subject: [PATCH 04/22] web-types transformation improvements --- src/cli/analyze/analyze-cli-command.ts | 3 +- src/cli/analyzer-cli-config.ts | 1 + src/cli/cli.ts | 7 ++- src/transformers/transformer-config.ts | 1 + .../webtypes/webtypes-transformer.ts | 55 ++++++++++--------- 5 files changed, 40 insertions(+), 27 deletions(-) diff --git a/src/cli/analyze/analyze-cli-command.ts b/src/cli/analyze/analyze-cli-command.ts index df6595cf..0fd4a306 100644 --- a/src/cli/analyze/analyze-cli-command.ts +++ b/src/cli/analyze/analyze-cli-command.ts @@ -128,7 +128,8 @@ function transformResults(results: AnalyzerResult[] | AnalyzerResult, program: P inlineTypes: config.inlineTypes ?? false, visibility: config.visibility ?? "public", markdown: config.markdown, - cwd: config.cwd + cwd: config.cwd, + pathAsAbsolute: config.pathAsAbsolute }; if (format == "webtypes") { transformerConfig.webTypes = config.webtypesConfig ? JSON.parse(config.webtypesConfig) : null; diff --git a/src/cli/analyzer-cli-config.ts b/src/cli/analyzer-cli-config.ts index 0b2022f9..c7525dad 100644 --- a/src/cli/analyzer-cli-config.ts +++ b/src/cli/analyzer-cli-config.ts @@ -30,6 +30,7 @@ export interface AnalyzerCliConfig { ts?: typeof tsModule; cwd?: string; + pathAsAbsolute?: boolean; webtypesConfig?: string; } diff --git a/src/cli/cli.ts b/src/cli/cli.ts index 992ece93..da70b9f6 100644 --- a/src/cli/cli.ts +++ b/src/cli/cli.ts @@ -103,7 +103,12 @@ o {tagname}: The element's tag name`, string: true, hidden: true }) - .option("webtypes-config", { + .option("pathAsAbsolute", { + describe: "Consider paths as absolute: don't add './' in front of paths", + boolean: true, + hidden: true + }) + .option("webtypesConfig", { describe: "WebTypes header configuration", string: true }) diff --git a/src/transformers/transformer-config.ts b/src/transformers/transformer-config.ts index f41dbe04..3fac5078 100644 --- a/src/transformers/transformer-config.ts +++ b/src/transformers/transformer-config.ts @@ -3,6 +3,7 @@ import { GenericContributions } from "./webtypes/webtypes-schema"; export interface TransformerConfig { cwd?: string; + pathAsAbsolute?: boolean; visibility?: VisibilityKind; markdown?: { titleLevel?: number; // deprecated diff --git a/src/transformers/webtypes/webtypes-transformer.ts b/src/transformers/webtypes/webtypes-transformer.ts index 3516ca16..a29db1a1 100644 --- a/src/transformers/webtypes/webtypes-transformer.ts +++ b/src/transformers/webtypes/webtypes-transformer.ts @@ -26,6 +26,7 @@ export const webtypesTransformer: TransformerFunction = (results: AnalyzerResult // Transform all definitions into "tags" const elements = definitions.map(d => definitionToHTMLElement(d, checker, config)); + const cssProperties = definitions.map(d => definitionToCssProperties(d)).reduce((acc, cur) => [...acc, ...cur], []); const webTypeConfig = config.webTypes; @@ -33,7 +34,7 @@ export const webtypesTransformer: TransformerFunction = (results: AnalyzerResult $schema: "http://json.schemastore.org/web-types", name: webTypeConfig?.name ?? "", version: webTypeConfig?.version ?? "", - ...(webTypeConfig?.framework ? { name: webTypeConfig?.framework } : {}), + ...(webTypeConfig?.framework ? { framework: webTypeConfig?.framework } : {}), ...(webTypeConfig?.["js-types-syntax"] ? { "js-types-syntax": webTypeConfig?.["js-types-syntax"] } : {}), ...(webTypeConfig?.["default-icon"] ? { "default-icon": webTypeConfig?.["default-icon"] } : {}), ...(webTypeConfig?.["framework-config"] ? { "framework-config": webTypeConfig?.["framework-config"] } : {}), @@ -41,6 +42,9 @@ export const webtypesTransformer: TransformerFunction = (results: AnalyzerResult contributions: { html: { elements: elements + }, + css: { + properties: cssProperties } } }; @@ -48,6 +52,12 @@ export const webtypesTransformer: TransformerFunction = (results: AnalyzerResult return JSON.stringify(webtypesJson, null, 4); }; +function definitionToCssProperties(definition: ComponentDefinition): CssProperty[] { + if (!definition.declaration) return []; + + return arrayDefined(definition.declaration.cssProperties.map(e => componentCssPropertiesToAttr(e))); +} + function definitionToHTMLElement(definition: ComponentDefinition, checker: TypeChecker, config: TransformerConfig): HtmlElement { const declaration = definition.declaration; @@ -68,8 +78,7 @@ function definitionToHTMLElement(definition: ComponentDefinition, checker: TypeC // Build source section const node = getFirst(definition.identifierNodes); - const fileName = node?.getSourceFile().fileName; - const path = fileName != null && config.cwd != null ? `./${relative(config.cwd, fileName)}` : undefined; + const path = getRelativePath(node?.getSourceFile().fileName, config); if (node?.getText() && path) { build.source = { @@ -79,13 +88,13 @@ function definitionToHTMLElement(definition: ComponentDefinition, checker: TypeC } // Build attributes - const customElementAttributes = arrayDefined(declaration.members.map(d => componentMemberToAttr(d.attrName, d, checker, config))); + const customElementAttributes = arrayDefined(declaration.members.map(d => componentMemberToAttr(d.attrName, true, d, checker, config))); if (customElementAttributes.length > 0) build.attributes = customElementAttributes; const js: Js = {}; // Build properties - const customElementProperties = arrayDefined(declaration.members.map(d => componentMemberToAttr(d.propName, d, checker, config))); + const customElementProperties = arrayDefined(declaration.members.map(d => componentMemberToAttr(d.propName, false, d, checker, config))); if (customElementProperties.length > 0) js.properties = customElementProperties; // Build events @@ -94,17 +103,13 @@ function definitionToHTMLElement(definition: ComponentDefinition, checker: TypeC if (js.properties || js.events) build.js = js; - // Build css properties - const cssProperties = arrayDefined(declaration.cssProperties.map(e => componentCssPropertiesToAttr(e, checker, config))); - if (cssProperties.length > 0) { - build.css = { - properties: cssProperties - }; - } - return build; } +function getRelativePath(fileName: string | undefined, config: TransformerConfig): string | undefined { + return fileName != null && config.cwd != null ? `${config.pathAsAbsolute ? "" : "./"}${relative(config.cwd, fileName)}` : undefined; +} + function componentEventToAttr(event: ComponentEvent, checker: TypeChecker, config: TransformerConfig): GenericJsContribution { // console.log(event); const builtEvent: GenericJsContribution = { @@ -116,7 +121,7 @@ function componentEventToAttr(event: ComponentEvent, checker: TypeChecker, confi return builtEvent; } -function componentCssPropertiesToAttr(cssProperty: ComponentCssProperty, checker: TypeChecker, config: TransformerConfig): CssProperty { +function componentCssPropertiesToAttr(cssProperty: ComponentCssProperty): CssProperty { const builtCssProp: CssProperty = { name: cssProperty.name }; @@ -128,6 +133,7 @@ function componentCssPropertiesToAttr(cssProperty: ComponentCssProperty, checker function componentMemberToAttr( propName: string | undefined, + isAttribute: boolean, member: ComponentMember, checker: TypeChecker, config: TransformerConfig @@ -136,22 +142,15 @@ function componentMemberToAttr( return undefined; } - // const isFunction = member.attrName == null; - const types: string[] | string = getTypeHintFromType(member.typeHint ?? member.type?.(), checker, config)?.split(" | ") ?? []; - // if (isFunction) { - // types = []; // TODO find a way to support function signatures, types includes signature as string - // } - // - // if (types.length == 1) { - // types = types[0]; - // } + const valueRequired = !(isAttribute && isBoolean(types)); const attr: HtmlAttribute = { name: propName, + required: !!member.required, value: { - type: types, - required: new Boolean(member.required).valueOf(), + type: types && Array.isArray(types) && types.length == 1 ? types[0] : types, + required: valueRequired, ...(member.default !== undefined ? { default: JSON.stringify(member.default) } : {}) }, ...(member.deprecated !== undefined ? { deprecated: true } : {}) @@ -161,3 +160,9 @@ function componentMemberToAttr( return attr; } + +function isBoolean(type: string | string[]): boolean { + if (Array.isArray(type)) return type.some(t => t == "boolean"); + + return type == "boolean"; +} From 22b1505d93a768549192574880fb503b7f21cca1 Mon Sep 17 00:00:00 2001 From: jpradelle Date: Tue, 18 Jan 2022 17:31:55 +0100 Subject: [PATCH 05/22] Add web-types documentation --- README.md | 535 ++++++++++++++++++++++++----------------------- doc/web-types.md | 85 ++++++++ 2 files changed, 358 insertions(+), 262 deletions(-) create mode 100644 doc/web-types.md diff --git a/README.md b/README.md index d5c62fa7..e2bb97b5 100644 --- a/README.md +++ b/README.md @@ -1,262 +1,273 @@ -

web-component-analyzer

- -

- Downloads per month - NPM Version - Dependencies - Contributors -

- -

- Web component analyzer GIF -

- -`web-component-analyzer` is a CLI that makes it possible to easily analyze web components. It analyzes your code and jsdoc in order to extract `properties`, `attributes`, `methods`, `events`, `slots`, `css shadow parts` and `css custom properties`. Works with both javascript and typescript. - -Try the online playground [here](https://runem.github.io/web-component-analyzer/) - -In addition to [vanilla web components](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements) this tool supports web components built with the following libraries: - -- [lit-element](https://github.com/Polymer/lit-element) -- [polymer](https://github.com/Polymer/polymer) -- [stencil](https://github.com/ionic-team/stencil) (partial) -- [lwc](https://github.com/salesforce/lwc) -- [open an issue for library requests](https://github.com/runem/web-component-analyzer/issues) - -[![-----------------------------------------------------](https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/colored.png)](#installation) - -## ➤ Installation - - -```bash -$ npm install -g web-component-analyzer -``` - -**or** - - -```bash -$ npx web-component-analyzer src -``` - -[![-----------------------------------------------------](https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/colored.png)](#usage) - -## ➤ Usage - - -```bash -$ wca analyze -$ wca analyze src --format markdown -$ wca analyze "src/**/*.{js,ts}" --outDir components -$ wca analyze my-element.js --outFile custom-elements.json -$ wca analyze --outFiles {dir}/custom-element.json -``` - - - -The analyze command analyses an optional `` and emits the output to the console as default. When the `` is omitted it will find all components excluding `node_modules`. The default format is `markdown`. - -### Options - - -| Option | Type | Description | -| --------------------------- | -------------------------------- | ---------------------------------------------------------------------------- | -| `--format ` | `markdown` \| `json` \| `vscode` | Specify output format. Default is `markdown`. | -| `--outDir ` | `directory path` | Direct output to a directory where each file corresponds to a web component. | -| `--outFile ` | `file path` | Concatenate and emit output to a single file. | -| `--outFiles ` | `file path with pattern` | Emit output to multiple files using a pattern. Available substitutions:
**{dir}**: The directory of the component
**{filename}**: The filename (without ext) of the component
**{tagname}**: The element's tag name | -| `--visibility ` | `public` \| `protected` \| `private` | The mininmum member visibility to output. Default is `public`. | -| `--features ` | `member` \| `method` \| `cssproperty` \| `csspart` \| `event` \| `slot` | Choose specific features to output. Multiple features are given seperated by a space. All features are enabled as default.
**Example**: `--features member slot event` | -| `--dry` | `boolean` | Don't write any files | - -[![-----------------------------------------------------](https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/colored.png)](#api) - -## ➤ Output Formats - -### json - - -```bash -wca analyze src --format json --outFile custom-elements.json -``` - -Try the online playground [here](https://runem.github.io/web-component-analyzer?format=json) - -This json format is for experimental and demo purposes, and is still being actively discussed. You can expect changes to this format. Please follow and contribute to the discussion at: - -- https://github.com/webcomponents/custom-elements-json -- https://github.com/w3c/webcomponents/issues/776 - -### markdown - - -```bash -wca analyze src --format markdown --outDir readme -``` - -Try the online playground [here](https://runem.github.io/web-component-analyzer?format=markdown) - -Web Component Analyzer can output markdown documentation of your web components. This can either be output into a single file using `--outFile` or into multiple files using `--outDir`. - -### vscode - - -```bash -wca analyze src --format vscode --outFile vscode-html-custom-data.json -``` - -VSCode supports a JSON format called [vscode custom data](https://github.com/microsoft/vscode-custom-data) for the built in html editor which is set using `html.customData` vscode setting. Web Component Analyzer can output this format. - -[![-----------------------------------------------------](https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/colored.png)](#how-does-this-tool-analyze-my-components) - -[![-----------------------------------------------------](https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/colored.png)](#how-to-document-your-components-using-jsdoc) - -## ➤ How to document your components using JSDoc - -In addition to analyzing the code of your components, this library also use JSDoc to construct the documentation. It's especially a good idea to use JSDoc for documenting `slots`, `events`, `css custom properties` and `css shadow parts` as these not analyzed statically by this tool as of now (except when constructing a CustomEvent within your component). - -Here's an example including all supported JSDoc tags. All JSDoc tags are on the the form `@tag {type} name - comment` and `@tag {type} [name=default] - comment`. - - -```javascript -/** - * Here is a description of my web component. - * - * @element my-element - * - * @fires change - This jsdoc tag makes it possible to document events. - * @fires submit - * - * @attr {Boolean} disabled - This jsdoc tag documents an attribute. - * @attr {on|off} switch - Here is an attribute with either the "on" or "off" value. - * @attr [my-attr=default value] - * - * @prop {String} myProp - You can use this jsdoc tag to document properties. - * @prop value - * - * @slot - This is an unnamed slot (the default slot) - * @slot start - This is a slot named "start". - * @slot end - * - * @cssprop --main-bg-color - This jsdoc tag can be used to document css custom properties. - * @cssprop [--main-color=red] - - * @csspart container - */ -class MyElement extends HTMLElement { - - /** - * This is a description of a property with an attribute with exactly the same name: "color". - * @type {"red"|"green"|"blue"} - * @attr - */ - color = "red"; - - /** - * This is a description of a property with an attribute called "my-prop". - * @type {number} - * @deprecated - * @attr my-prop - */ - myProp = 10 - - static get observedAttributes () { - return [ - /** - * The header text of this element - */ - "header" - ]; - } - -} -``` - -### Overview of supported JSDoc tags - - -| JSDoc Tag | Description | -| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | -| `@element` | Gives your component a tag name. This JSDoc tag is useful if your 'customElements.define` is called dynamically eg. using a custom function. | -| `@fires` | Documents events. | -| `@slot` | Documents slots. Using an empty name here documents the unnamed (default) slot. | -| `@attr` or `@attribute` | Documents an attribute on your component. | -| `@prop` or `@property` | Documents a property on your component. | -| `@cssprop` or `@cssproperty` | Documents a css custom property on your component. | -| `@csspart` | Documents a css shadow part on your component. | - -[![-----------------------------------------------------](https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/colored.png)](#contributors) - -## ➤ How does this tool analyze my components? - -This tool extract information about your components by looking at your code directly and by looking at your JSDoc comments. - -**Code**: Web Component Analyzer supports multiple libraries. [Click here](https://github.com/runem/web-component-analyzer/blob/master/ANALYZE.md) for an overview of how each library is analyzed. - -**JSDoc**: Read next section to learn more about how JSDoc is analyzed. - -## ➤ API - -You can also directly use the underlying functionality of this tool if you don't want to use the CLI. Web Component Analyzer analyzes Typescript source files, so you will have to include the Typescript parser. Here are some examples of how to use the API. - -### Analyze Typescript source file - - -```typescript -import { analyzeSourceFile } from "web-component-analyzer"; - -const result = analyzeSourceFile(sourceFile, { checker }); -``` - -### Analyze text - - -```javascript -import { analyzeText } from "web-component-analyzer"; - -const code = `class MyElement extends HTMLElement { - -} - -customElements.define("my-element", MyElement); -`; - - -const { results, program } = analyzeText(code); -// or -const { results, program } = analyzeText([ - { fileName: "file1.js", text: code }, - { fileName: "file2.js", text: "..." }, // these files can depend on each other - { fileName: "file3.js", text: "...", analyze: false } -]); -// each result in "results" is the result of analyzing the corresponding text where "analyze" is not false -``` - -### Transform the result - - -```javascript -import { transformAnalyzerResult } from "web-component-analyzer"; - -const result = // the result of analyzing the component using one of the above functions - -const format = "markdown"; // or "json" - -const output = transformAnalyzerResult(format, result, program); - -// "output" is now a string containing the result of the "markdown" transformer -``` - -[![-----------------------------------------------------](https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/colored.png)](#how-does-this-tool-analyze-my-components) - -## ➤ Contributors - -| [Rune Mehlsen](https://github.com/runem) | -| :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | -| [Rune Mehlsen](https://github.com/runem) | - -[![-----------------------------------------------------](https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/colored.png)](#license) - -## ➤ License - -Licensed under [MIT](https://opensource.org/licenses/MIT). +

web-component-analyzer

+ +

+ Downloads per month + NPM Version + Dependencies + Contributors +

+ +

+ Web component analyzer GIF +

+ +`web-component-analyzer` is a CLI that makes it possible to easily analyze web components. It analyzes your code and jsdoc in order to extract `properties`, `attributes`, `methods`, `events`, `slots`, `css shadow parts` and `css custom properties`. Works with both javascript and typescript. + +Try the online playground [here](https://runem.github.io/web-component-analyzer/) + +In addition to [vanilla web components](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements) this tool supports web components built with the following libraries: + +- [lit-element](https://github.com/Polymer/lit-element) +- [polymer](https://github.com/Polymer/polymer) +- [stencil](https://github.com/ionic-team/stencil) (partial) +- [lwc](https://github.com/salesforce/lwc) +- [open an issue for library requests](https://github.com/runem/web-component-analyzer/issues) + +[![-----------------------------------------------------](https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/colored.png)](#installation) + +## ➤ Installation + + +```bash +$ npm install -g web-component-analyzer +``` + +**or** + + +```bash +$ npx web-component-analyzer src +``` + +[![-----------------------------------------------------](https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/colored.png)](#usage) + +## ➤ Usage + + +```bash +$ wca analyze +$ wca analyze src --format markdown +$ wca analyze "src/**/*.{js,ts}" --outDir components +$ wca analyze my-element.js --outFile custom-elements.json +$ wca analyze --outFiles {dir}/custom-element.json +``` + + + +The analyze command analyses an optional `` and emits the output to the console as default. When the `` is omitted it will find all components excluding `node_modules`. The default format is `markdown`. + +### Options + + +| Option | Type | Description | +|-----------------------------|--------------------------| ---------------------------------------------------------------------------- | +| `--format ` | `markdown` \ | `json` \| `vscode` \| `webtypes` | Specify output format. Default is `markdown`. | +| `--outDir ` | `directory path` | Direct output to a directory where each file corresponds to a web component. | +| `--outFile ` | `file path` | Concatenate and emit output to a single file. | +| `--outFiles ` | `file path with pattern` | Emit output to multiple files using a pattern. Available substitutions:
**{dir}**: The directory of the component
**{filename}**: The filename (without ext) of the component
**{tagname}**: The element's tag name | +| `--visibility ` | `public` \ | `protected` \| `private` | The mininmum member visibility to output. Default is `public`. | +| `--features ` | `member` \ | `method` \| `cssproperty` \| `csspart` \| `event` \| `slot` | Choose specific features to output. Multiple features are given seperated by a space. All features are enabled as default.
**Example**: `--features member slot event` | +| `--dry` | `boolean` | Don't write any files | + +[![-----------------------------------------------------](https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/colored.png)](#api) + +## ➤ Output Formats + +### json + + +```bash +wca analyze src --format json --outFile custom-elements.json +``` + +Try the online playground [here](https://runem.github.io/web-component-analyzer?format=json) + +This json format is for experimental and demo purposes, and is still being actively discussed. You can expect changes to this format. Please follow and contribute to the discussion at: + +- https://github.com/webcomponents/custom-elements-json +- https://github.com/w3c/webcomponents/issues/776 + +### markdown + + +```bash +wca analyze src --format markdown --outDir readme +``` + +Try the online playground [here](https://runem.github.io/web-component-analyzer?format=markdown) + +Web Component Analyzer can output markdown documentation of your web components. This can either be output into a single file using `--outFile` or into multiple files using `--outDir`. + +### vscode + + +```bash +wca analyze src --format vscode --outFile vscode-html-custom-data.json +``` + +VSCode supports a JSON format called [vscode custom data](https://github.com/microsoft/vscode-custom-data) for the built in html editor which is set using `html.customData` vscode setting. Web Component Analyzer can output this format. + +[![-----------------------------------------------------](https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/colored.png)](#how-does-this-tool-analyze-my-components) + +[![-----------------------------------------------------](https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/colored.png)](#how-to-document-your-components-using-jsdoc) + +### webtypes + + +```bash +wca analyze src --format webtypes --outFile web-types-custom.json --webtypesConfig='{"name": "web-types-custom", "version": "0.0.1", "description-markup": "markdown"}' +``` + +Web-types format is a description for IDE completion, see [web-types](https://github.com/JetBrains/web-types/tree/master/packages) + +See [web-types dedicated page](./doc/web-types.md) for project setup. + +## ➤ How to document your components using JSDoc + +In addition to analyzing the code of your components, this library also use JSDoc to construct the documentation. It's especially a good idea to use JSDoc for documenting `slots`, `events`, `css custom properties` and `css shadow parts` as these not analyzed statically by this tool as of now (except when constructing a CustomEvent within your component). + +Here's an example including all supported JSDoc tags. All JSDoc tags are on the the form `@tag {type} name - comment` and `@tag {type} [name=default] - comment`. + + +```javascript +/** + * Here is a description of my web component. + * + * @element my-element + * + * @fires change - This jsdoc tag makes it possible to document events. + * @fires submit + * + * @attr {Boolean} disabled - This jsdoc tag documents an attribute. + * @attr {on|off} switch - Here is an attribute with either the "on" or "off" value. + * @attr [my-attr=default value] + * + * @prop {String} myProp - You can use this jsdoc tag to document properties. + * @prop value + * + * @slot - This is an unnamed slot (the default slot) + * @slot start - This is a slot named "start". + * @slot end + * + * @cssprop --main-bg-color - This jsdoc tag can be used to document css custom properties. + * @cssprop [--main-color=red] + + * @csspart container + */ +class MyElement extends HTMLElement { + + /** + * This is a description of a property with an attribute with exactly the same name: "color". + * @type {"red"|"green"|"blue"} + * @attr + */ + color = "red"; + + /** + * This is a description of a property with an attribute called "my-prop". + * @type {number} + * @deprecated + * @attr my-prop + */ + myProp = 10 + + static get observedAttributes () { + return [ + /** + * The header text of this element + */ + "header" + ]; + } + +} +``` + +### Overview of supported JSDoc tags + + +| JSDoc Tag | Description | +| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | +| `@element` | Gives your component a tag name. This JSDoc tag is useful if your 'customElements.define` is called dynamically eg. using a custom function. | +| `@fires` | Documents events. | +| `@slot` | Documents slots. Using an empty name here documents the unnamed (default) slot. | +| `@attr` or `@attribute` | Documents an attribute on your component. | +| `@prop` or `@property` | Documents a property on your component. | +| `@cssprop` or `@cssproperty` | Documents a css custom property on your component. | +| `@csspart` | Documents a css shadow part on your component. | + +[![-----------------------------------------------------](https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/colored.png)](#contributors) + +## ➤ How does this tool analyze my components? + +This tool extract information about your components by looking at your code directly and by looking at your JSDoc comments. + +**Code**: Web Component Analyzer supports multiple libraries. [Click here](https://github.com/runem/web-component-analyzer/blob/master/ANALYZE.md) for an overview of how each library is analyzed. + +**JSDoc**: Read next section to learn more about how JSDoc is analyzed. + +## ➤ API + +You can also directly use the underlying functionality of this tool if you don't want to use the CLI. Web Component Analyzer analyzes Typescript source files, so you will have to include the Typescript parser. Here are some examples of how to use the API. + +### Analyze Typescript source file + + +```typescript +import { analyzeSourceFile } from "web-component-analyzer"; + +const result = analyzeSourceFile(sourceFile, { checker }); +``` + +### Analyze text + + +```javascript +import { analyzeText } from "web-component-analyzer"; + +const code = `class MyElement extends HTMLElement { + +} + +customElements.define("my-element", MyElement); +`; + + +const { results, program } = analyzeText(code); +// or +const { results, program } = analyzeText([ + { fileName: "file1.js", text: code }, + { fileName: "file2.js", text: "..." }, // these files can depend on each other + { fileName: "file3.js", text: "...", analyze: false } +]); +// each result in "results" is the result of analyzing the corresponding text where "analyze" is not false +``` + +### Transform the result + + +```javascript +import { transformAnalyzerResult } from "web-component-analyzer"; + +const result = // the result of analyzing the component using one of the above functions + +const format = "markdown"; // or "json" + +const output = transformAnalyzerResult(format, result, program); + +// "output" is now a string containing the result of the "markdown" transformer +``` + +[![-----------------------------------------------------](https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/colored.png)](#how-does-this-tool-analyze-my-components) + +## ➤ Contributors + +| [Rune Mehlsen](https://github.com/runem) | +| :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| [Rune Mehlsen](https://github.com/runem) | + +[![-----------------------------------------------------](https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/colored.png)](#license) + +## ➤ License + +Licensed under [MIT](https://opensource.org/licenses/MIT). diff --git a/doc/web-types.md b/doc/web-types.md new file mode 100644 index 00000000..a1c2dcad --- /dev/null +++ b/doc/web-types.md @@ -0,0 +1,85 @@ +# Web-types + +## Project setup + +### For Lit + +Generated web-types are working with the generic `lit-web-types` library. You need to add it on your project. + + +```bash +npm i lit-web-types -D +``` + +Generate your components descriptions with wca + + +```bash +wca analyze src --format webtypes --outFile web-types-custom.json --webtypesConfig='{"name": "web-types-custom", "version": "0.0.1", "description-markup": "markdown", "framework": "lit"}' +``` + +`--webtypesConfig` is a json object of web-types root parameters. Working with lit, `framework` must have `lit` value. +See "Web types config parameters" documentation section for more info. + +Link your generated web-types file in your package.json + +```json +{ + ..., + "web-types": [ + "./web-types-custom.json" + ] +} +``` + +After the first setup on Intellij, IDE restart might be needed to enable components completion. + +### For Polymer + +Generated web-types are working with the generic `polymer-web-types` library. You need to add it on your project. + + +```bash +npm i polymer-web-types -D +``` + +Generate your components descriptions with wca + + +```bash +wca analyze src --format webtypes --outFile web-types-custom.json --webtypesConfig='{"name": "web-types-custom", "version": "0.0.1", "description-markup": "markdown", "framework": "@polymer/polymer"}' +``` + +`--webtypesConfig` is a json object of web-types root parameters. Working with polymer, `framework` must have `@polymer/polymer` value. +See "Web types config parameters" documentation section for more info. + +Link your generated web-types file in your package.json + +```json +{ + ..., + "web-types": [ + "./web-types-custom.json" + ] +} +``` + +After the first setup on Intellij, IDE restart might be needed to enable components completion. + +## Web types config parameters + +`--webtypesConfig` parameter is a json object containing web-types file root parameter. + +Available parameters: + +| Name | Description | +| ------------------ | ------------------------------------------------------------------------------------------------------------- | +| name | Name of library, mandatory option | +| version | Version of the library, for which Web-Types are provided, mandatory option | +| framework | See [http://json.schemastore.org/web-types](http://json.schemastore.org/web-types) framework section | +| js-types-syntax | See [http://json.schemastore.org/web-types](http://json.schemastore.org/web-types) js-types-syntax section | +| description-markup | See [http://json.schemastore.org/web-types](http://json.schemastore.org/web-types) description-markup section | +| framework-config | See [http://json.schemastore.org/web-types](http://json.schemastore.org/web-types) framework-config section | +| default-icon | See [http://json.schemastore.org/web-types](http://json.schemastore.org/web-types) default-icon section | + +See [web-types project:](https://github.com/JetBrains/web-types) for more info. From 11f1b32c600fed3ea9796f6b9b0596135ed8b0d9 Mon Sep 17 00:00:00 2001 From: jpradelle Date: Wed, 19 Jan 2022 14:13:22 +0100 Subject: [PATCH 06/22] Fix readme webtype section position --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index e2bb97b5..1b2f89d3 100644 --- a/README.md +++ b/README.md @@ -107,10 +107,6 @@ wca analyze src --format vscode --outFile vscode-html-custom-data.json VSCode supports a JSON format called [vscode custom data](https://github.com/microsoft/vscode-custom-data) for the built in html editor which is set using `html.customData` vscode setting. Web Component Analyzer can output this format. -[![-----------------------------------------------------](https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/colored.png)](#how-does-this-tool-analyze-my-components) - -[![-----------------------------------------------------](https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/colored.png)](#how-to-document-your-components-using-jsdoc) - ### webtypes @@ -122,6 +118,10 @@ Web-types format is a description for IDE completion, see [web-types](https://gi See [web-types dedicated page](./doc/web-types.md) for project setup. +[![-----------------------------------------------------](https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/colored.png)](#how-does-this-tool-analyze-my-components) + +[![-----------------------------------------------------](https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/colored.png)](#how-to-document-your-components-using-jsdoc) + ## ➤ How to document your components using JSDoc In addition to analyzing the code of your components, this library also use JSDoc to construct the documentation. It's especially a good idea to use JSDoc for documenting `slots`, `events`, `css custom properties` and `css shadow parts` as these not analyzed statically by this tool as of now (except when constructing a CustomEvent within your component). From b654d6913583d38744fb791756c348226503afaf Mon Sep 17 00:00:00 2001 From: jpradelle Date: Wed, 19 Jan 2022 14:50:19 +0100 Subject: [PATCH 07/22] webtypes: add priority, fix boolean attributes --- src/transformers/webtypes/webtypes-transformer.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/transformers/webtypes/webtypes-transformer.ts b/src/transformers/webtypes/webtypes-transformer.ts index a29db1a1..33d1851b 100644 --- a/src/transformers/webtypes/webtypes-transformer.ts +++ b/src/transformers/webtypes/webtypes-transformer.ts @@ -111,7 +111,6 @@ function getRelativePath(fileName: string | undefined, config: TransformerConfig } function componentEventToAttr(event: ComponentEvent, checker: TypeChecker, config: TransformerConfig): GenericJsContribution { - // console.log(event); const builtEvent: GenericJsContribution = { name: event.name }; @@ -148,6 +147,7 @@ function componentMemberToAttr( const attr: HtmlAttribute = { name: propName, required: !!member.required, + priority: member.visibility == "private" || member.visibility == "protected" ? "lowest" : "normal", value: { type: types && Array.isArray(types) && types.length == 1 ? types[0] : types, required: valueRequired, @@ -162,7 +162,7 @@ function componentMemberToAttr( } function isBoolean(type: string | string[]): boolean { - if (Array.isArray(type)) return type.some(t => t == "boolean"); + if (Array.isArray(type)) return type.some(t => t && t.includes("boolean")); - return type == "boolean"; + return type ? type.includes("boolean") : false; } From 30d45827ae7cce36c4d483cea25c4361e922385f Mon Sep 17 00:00:00 2001 From: jpradelle Date: Wed, 19 Jan 2022 18:18:43 +0100 Subject: [PATCH 08/22] Add web-types package keyword --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 14bd33ec..4aace1e9 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,8 @@ "keywords": [ "web components", "web", - "components" + "components", + "web-types" ], "contributors": [ { From 71869be768c3e74269f71b253423516e06ef63f9 Mon Sep 17 00:00:00 2001 From: jpradelle Date: Thu, 20 Jan 2022 16:18:25 +0100 Subject: [PATCH 09/22] Code cleanup --- src/transformers/webtypes/webtypes-schema.ts | 6 ------ src/transformers/webtypes/webtypes-transformer.ts | 4 ++-- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/transformers/webtypes/webtypes-schema.ts b/src/transformers/webtypes/webtypes-schema.ts index 8737d046..bccddf99 100644 --- a/src/transformers/webtypes/webtypes-schema.ts +++ b/src/transformers/webtypes/webtypes-schema.ts @@ -27,7 +27,6 @@ export type Priority = "lowest" | "low" | "normal" | "high" | "highest"; * Relative path to icon */ export type Icon = string; -// export type Html = GenericContributionsHost; export type GenericContributions = GenericContributionOrProperty[] | GenericContributionOrProperty; export type GenericContributionOrProperty = string | number | boolean | GenericContribution | Source; export type GenericContribution = TypedContribution; @@ -106,9 +105,6 @@ export interface FrameworkConfig { [k: string]: NameConverters; }; } -export interface GenericContributionsHost { - [k: string]: GenericContributions; -} export interface SourceFile { file: string; @@ -127,8 +123,6 @@ export type Html = HtmlContributionHost; export interface HtmlElement extends BaseContribution, HtmlContributionHost {} export interface BaseContribution { - // #/definitions/base-contribution - // [k: string]: GenericContributions; name?: string; description?: string; // "description-sections"?: ; diff --git a/src/transformers/webtypes/webtypes-transformer.ts b/src/transformers/webtypes/webtypes-transformer.ts index 33d1851b..84c22903 100644 --- a/src/transformers/webtypes/webtypes-transformer.ts +++ b/src/transformers/webtypes/webtypes-transformer.ts @@ -98,7 +98,7 @@ function definitionToHTMLElement(definition: ComponentDefinition, checker: TypeC if (customElementProperties.length > 0) js.properties = customElementProperties; // Build events - const eventAttributes = arrayDefined(declaration.events.map(e => componentEventToAttr(e, checker, config))); + const eventAttributes = arrayDefined(declaration.events.map(e => componentEventToAttr(e))); if (eventAttributes.length > 0) js.events = eventAttributes; if (js.properties || js.events) build.js = js; @@ -110,7 +110,7 @@ function getRelativePath(fileName: string | undefined, config: TransformerConfig return fileName != null && config.cwd != null ? `${config.pathAsAbsolute ? "" : "./"}${relative(config.cwd, fileName)}` : undefined; } -function componentEventToAttr(event: ComponentEvent, checker: TypeChecker, config: TransformerConfig): GenericJsContribution { +function componentEventToAttr(event: ComponentEvent): GenericJsContribution { const builtEvent: GenericJsContribution = { name: event.name }; From f6ab6cf5ae60e08f3d0c2406cf5c93f42976610b Mon Sep 17 00:00:00 2001 From: jpradelle Date: Fri, 26 Aug 2022 12:20:41 +0200 Subject: [PATCH 10/22] Update webtypes configuration to support package.json configuration. Work inspired by @Atulin https://github.com/Atulin/web-component-analyzer/commit/0f8b0e5a474d2cd072e3791c7384a3672cfefa8a --- README.md | 20 ++- doc/web-types.md | 117 ++++++++++++++---- src/cli/analyze/analyze-cli-command.ts | 29 ++++- src/cli/analyzer-cli-config.ts | 3 +- src/cli/cli.ts | 1 + .../webtypes/webtypes-transformer.ts | 4 +- 6 files changed, 141 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 1b2f89d3..27299e71 100644 --- a/README.md +++ b/README.md @@ -111,12 +111,28 @@ VSCode supports a JSON format called [vscode custom data](https://github.com/mic ```bash -wca analyze src --format webtypes --outFile web-types-custom.json --webtypesConfig='{"name": "web-types-custom", "version": "0.0.1", "description-markup": "markdown"}' +wca analyze src --format webtypes --outFile web-types-custom.json +``` + +To configure web-types (name, version, etc.) add this section to your `package.json`. `name` and `version` fields are required if wca is not ran from npm `package.json` `scripts` section. +Otherwise it will take `package.json` project `name` and `version`. + +You can use the `wca-config` section to configure other options as well. + +```json +"wca": { + "webtypesConfig": { + "name": "your-project-name", + "version": "0.0.1", + "framework": "lit", + "description-markup": "markdown" + } +} ``` Web-types format is a description for IDE completion, see [web-types](https://github.com/JetBrains/web-types/tree/master/packages) -See [web-types dedicated page](./doc/web-types.md) for project setup. +See [web-component-analyzer web-types dedicated page](./doc/web-types.md) for project setup. [![-----------------------------------------------------](https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/colored.png)](#how-does-this-tool-analyze-my-components) diff --git a/doc/web-types.md b/doc/web-types.md index a1c2dcad..ade5ae4e 100644 --- a/doc/web-types.md +++ b/doc/web-types.md @@ -1,5 +1,42 @@ # Web-types +## web-component-analyzer usage + +### With package.json configuration + +You can add a `wca` section in your `package.json` to configure wca for web-types build. + +```json +"wca": { + "webtypesConfig": { + "name": "your-project-name", + "version": "0.0.1", + "framework": "lit", + "description-markup": "markdown" + } +} +``` + +To run wca then use command: + +```bash +wca analyze src --format webtypes --outFile web-types-custom.json +``` + +If you run `wca` from project npm `scripts` section of `package.json`, you can omit `name` and `version` property +in `webtypesConfig`, this will take package `name` and `version` by default. + +### With command line only + +You can also avoid updating `package.json` `wca` section and use only command line by providing a json configuration to `--webtypesConfig` argument: + +```bash +wca analyze src --format webtypes --outFile web-types-custom.json --webtypesConfig='{"name": "your-project-name", "version": "0.0.1", "framework": "lit", "description-markup": "markdown"}' +``` + +If you run `wca` from project npm `scripts` section of `package.json`, you can omit `name` and `version` property +in `webtypesConfig`, this will take package `name` and `version` by default. + ## Project setup ### For Lit @@ -11,25 +48,40 @@ Generated web-types are working with the generic `lit-web-types` library. You ne npm i lit-web-types -D ``` +Add `wca` section in your `package.json`: + +```json +"wca": { + "webtypesConfig": { + "framework": "lit", + "description-markup": "markdown" + } +} +``` + +Working with lit, `framework` must have `lit` value. See [Web types config parameters](#web-types-config-parameters) documentation section for more info. + +Add `scripts` section in your `package.json`: + +```json +"scripts": { + "web-types": "wca analyze src --format webtypes --outFile web-types.json" +} +``` + Generate your components descriptions with wca ```bash -wca analyze src --format webtypes --outFile web-types-custom.json --webtypesConfig='{"name": "web-types-custom", "version": "0.0.1", "description-markup": "markdown", "framework": "lit"}' +npm web-types ``` -`--webtypesConfig` is a json object of web-types root parameters. Working with lit, `framework` must have `lit` value. -See "Web types config parameters" documentation section for more info. - -Link your generated web-types file in your package.json +If your web-types is not named `web-types.json` and placed at root of your project, you need to declare it in your `package.json` ```json -{ - ..., - "web-types": [ - "./web-types-custom.json" - ] -} +"web-types": [ + "..../web-types-custom.json" +] ``` After the first setup on Intellij, IDE restart might be needed to enable components completion. @@ -43,39 +95,54 @@ Generated web-types are working with the generic `polymer-web-types` library. Yo npm i polymer-web-types -D ``` +Add `wca` section in your `package.json`: + +```json +"wca": { + "webtypesConfig": { + "framework": "@polymer/polymer", + "description-markup": "markdown" + } +} +``` + +Working with lit, `framework` must have `@polymer/polymer` value. See [Web types config parameters](#web-types-config-parameters) documentation section for more info. + +Add `scripts` section in your `package.json`: + +```json +"scripts": { + "web-types": "wca analyze src --format webtypes --outFile web-types.json" +} +``` + Generate your components descriptions with wca ```bash -wca analyze src --format webtypes --outFile web-types-custom.json --webtypesConfig='{"name": "web-types-custom", "version": "0.0.1", "description-markup": "markdown", "framework": "@polymer/polymer"}' +npm web-types ``` -`--webtypesConfig` is a json object of web-types root parameters. Working with polymer, `framework` must have `@polymer/polymer` value. -See "Web types config parameters" documentation section for more info. - -Link your generated web-types file in your package.json +If your web-types is not named `web-types.json` and placed at root of your project, you need to declare it in your `package.json` ```json -{ - ..., - "web-types": [ - "./web-types-custom.json" - ] -} +"web-types": [ + "..../web-types-custom.json" +] ``` After the first setup on Intellij, IDE restart might be needed to enable components completion. ## Web types config parameters -`--webtypesConfig` parameter is a json object containing web-types file root parameter. +`webtypesConfig` parameter is a json object containing web-types file root parameter. Available parameters: | Name | Description | | ------------------ | ------------------------------------------------------------------------------------------------------------- | -| name | Name of library, mandatory option | -| version | Version of the library, for which Web-Types are provided, mandatory option | +| name | Name of library | +| version | Version of the library, for which Web-Types are provided | | framework | See [http://json.schemastore.org/web-types](http://json.schemastore.org/web-types) framework section | | js-types-syntax | See [http://json.schemastore.org/web-types](http://json.schemastore.org/web-types) js-types-syntax section | | description-markup | See [http://json.schemastore.org/web-types](http://json.schemastore.org/web-types) description-markup section | diff --git a/src/cli/analyze/analyze-cli-command.ts b/src/cli/analyze/analyze-cli-command.ts index 0fd4a306..ac637dc0 100644 --- a/src/cli/analyze/analyze-cli-command.ts +++ b/src/cli/analyze/analyze-cli-command.ts @@ -38,9 +38,30 @@ Please follow and contribute to the discussion at: } if (config.format === "webtypes") { - const webTypesConfig = config.webtypesConfig ? JSON.parse(config.webtypesConfig) : null; - if (!webTypesConfig.name) throw makeCliError('Missing webtypes-config "name" property'); - if (!webTypesConfig.version) throw makeCliError('Missing webtypes-config "version" property'); + // Allow object being passed as JSON from command line + if (typeof config.webtypesConfig === "string") { + config.webtypesConfig = JSON.parse(config.webtypesConfig); + } + + if (!config?.webtypesConfig) throw makeCliError("Missing webtypes-config configuration"); + + if (!config.webtypesConfig.name) { + // Take package name if ran from npm script + if (process.env.npm_package_name) { + config.webtypesConfig.name = process.env.npm_package_name; + } else { + throw makeCliError('Missing webtypes-config "name" property'); + } + } + + if (!config.webtypesConfig.version) { + // Take package version if ran from npm script + if (process.env.npm_package_version) { + config.webtypesConfig.version = process.env.npm_package_version; + } else { + throw makeCliError('Missing webtypes-config "version" property'); + } + } } // If no "out" is specified, output to console @@ -132,7 +153,7 @@ function transformResults(results: AnalyzerResult[] | AnalyzerResult, program: P pathAsAbsolute: config.pathAsAbsolute }; if (format == "webtypes") { - transformerConfig.webTypes = config.webtypesConfig ? JSON.parse(config.webtypesConfig) : null; + transformerConfig.webTypes = config.webtypesConfig; } return transformAnalyzerResult(format, results, program, transformerConfig); diff --git a/src/cli/analyzer-cli-config.ts b/src/cli/analyzer-cli-config.ts index c7525dad..b151eeda 100644 --- a/src/cli/analyzer-cli-config.ts +++ b/src/cli/analyzer-cli-config.ts @@ -2,6 +2,7 @@ import * as tsModule from "typescript"; import { ComponentFeature } from "../analyze/types/features/component-feature"; import { VisibilityKind } from "../analyze/types/visibility-kind"; import { TransformerKind } from "../transformers/transformer-kind"; +import { WebTypesTransformerConfig } from "../transformers"; export interface AnalyzerCliConfig { glob?: string[]; @@ -32,5 +33,5 @@ export interface AnalyzerCliConfig { cwd?: string; pathAsAbsolute?: boolean; - webtypesConfig?: string; + webtypesConfig?: WebTypesTransformerConfig; } diff --git a/src/cli/cli.ts b/src/cli/cli.ts index da70b9f6..1e2fc6f2 100644 --- a/src/cli/cli.ts +++ b/src/cli/cli.ts @@ -9,6 +9,7 @@ import { log } from "./util/log"; */ export function cli(): void { const argv = yargs + .pkgConf("wca") .usage("Usage: $0 [glob..] [options]") .command({ command: ["analyze [glob..]", "$0"], diff --git a/src/transformers/webtypes/webtypes-transformer.ts b/src/transformers/webtypes/webtypes-transformer.ts index 84c22903..e3caad17 100644 --- a/src/transformers/webtypes/webtypes-transformer.ts +++ b/src/transformers/webtypes/webtypes-transformer.ts @@ -107,7 +107,9 @@ function definitionToHTMLElement(definition: ComponentDefinition, checker: TypeC } function getRelativePath(fileName: string | undefined, config: TransformerConfig): string | undefined { - return fileName != null && config.cwd != null ? `${config.pathAsAbsolute ? "" : "./"}${relative(config.cwd, fileName)}` : undefined; + return fileName != null && config.cwd != null + ? `${config.pathAsAbsolute ? "" : "./"}${relative(config.cwd, fileName)}`.replaceAll("\\", "/") + : undefined; } function componentEventToAttr(event: ComponentEvent): GenericJsContribution { From 92427fe6dad7af36b782caa79ed71fcf5b2c97db Mon Sep 17 00:00:00 2001 From: jpradelle Date: Fri, 26 Aug 2022 16:11:16 +0200 Subject: [PATCH 11/22] Webtypes PR fixes --- src/cli/analyze/analyze-cli-command.ts | 26 ++++++++++++++++++-------- src/cli/analyzer-cli-config.ts | 3 ++- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/cli/analyze/analyze-cli-command.ts b/src/cli/analyze/analyze-cli-command.ts index ac637dc0..3ea14d89 100644 --- a/src/cli/analyze/analyze-cli-command.ts +++ b/src/cli/analyze/analyze-cli-command.ts @@ -38,30 +38,40 @@ Please follow and contribute to the discussion at: } if (config.format === "webtypes") { + if (!config.webtypesConfig) throw makeCliError("Missing webtypes-config configuration"); + // Allow object being passed as JSON from command line + let cleanedConfiguration; if (typeof config.webtypesConfig === "string") { - config.webtypesConfig = JSON.parse(config.webtypesConfig); + try { + cleanedConfiguration = JSON.parse(config.webtypesConfig); + } catch (e) { + const message = e instanceof Error ? e.message : e; + throw makeCliError("webtypes-config JSON format issue: " + message + "\nReceived value: " + config.webtypesConfig); + } + } else { + cleanedConfiguration = config.webtypesConfig; } - if (!config?.webtypesConfig) throw makeCliError("Missing webtypes-config configuration"); - - if (!config.webtypesConfig.name) { + if (!cleanedConfiguration.name) { // Take package name if ran from npm script if (process.env.npm_package_name) { - config.webtypesConfig.name = process.env.npm_package_name; + cleanedConfiguration.name = process.env.npm_package_name; } else { throw makeCliError('Missing webtypes-config "name" property'); } } - if (!config.webtypesConfig.version) { + if (!cleanedConfiguration.version) { // Take package version if ran from npm script if (process.env.npm_package_version) { - config.webtypesConfig.version = process.env.npm_package_version; + cleanedConfiguration.version = process.env.npm_package_version; } else { throw makeCliError('Missing webtypes-config "version" property'); } } + + config.parsedWebtypesConfig = cleanedConfiguration; } // If no "out" is specified, output to console @@ -153,7 +163,7 @@ function transformResults(results: AnalyzerResult[] | AnalyzerResult, program: P pathAsAbsolute: config.pathAsAbsolute }; if (format == "webtypes") { - transformerConfig.webTypes = config.webtypesConfig; + transformerConfig.webTypes = config.parsedWebtypesConfig; } return transformAnalyzerResult(format, results, program, transformerConfig); diff --git a/src/cli/analyzer-cli-config.ts b/src/cli/analyzer-cli-config.ts index b151eeda..d1bf7ac5 100644 --- a/src/cli/analyzer-cli-config.ts +++ b/src/cli/analyzer-cli-config.ts @@ -33,5 +33,6 @@ export interface AnalyzerCliConfig { cwd?: string; pathAsAbsolute?: boolean; - webtypesConfig?: WebTypesTransformerConfig; + webtypesConfig?: string | WebTypesTransformerConfig; + parsedWebtypesConfig?: WebTypesTransformerConfig; } From fc64397e7263b9e610b3e76163fc03ea6a023e3d Mon Sep 17 00:00:00 2001 From: jpradelle Date: Mon, 29 Aug 2022 14:58:41 +0200 Subject: [PATCH 12/22] webtypes: fix boolean value requirement issue --- src/transformers/webtypes/webtypes-transformer.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/transformers/webtypes/webtypes-transformer.ts b/src/transformers/webtypes/webtypes-transformer.ts index e3caad17..f538e003 100644 --- a/src/transformers/webtypes/webtypes-transformer.ts +++ b/src/transformers/webtypes/webtypes-transformer.ts @@ -144,7 +144,6 @@ function componentMemberToAttr( } const types: string[] | string = getTypeHintFromType(member.typeHint ?? member.type?.(), checker, config)?.split(" | ") ?? []; - const valueRequired = !(isAttribute && isBoolean(types)); const attr: HtmlAttribute = { name: propName, @@ -152,7 +151,7 @@ function componentMemberToAttr( priority: member.visibility == "private" || member.visibility == "protected" ? "lowest" : "normal", value: { type: types && Array.isArray(types) && types.length == 1 ? types[0] : types, - required: valueRequired, + required: !isBoolean(types), ...(member.default !== undefined ? { default: JSON.stringify(member.default) } : {}) }, ...(member.deprecated !== undefined ? { deprecated: true } : {}) @@ -164,7 +163,7 @@ function componentMemberToAttr( } function isBoolean(type: string | string[]): boolean { - if (Array.isArray(type)) return type.some(t => t && t.includes("boolean")); + if (Array.isArray(type)) return type.some(t => t && t.toLowerCase().includes("boolean")); - return type ? type.includes("boolean") : false; + return type ? type.toLowerCase().includes("boolean") : false; } From d92fc8b8bcb59d4899db34d379dd045e95a7fbc0 Mon Sep 17 00:00:00 2001 From: jpradelle Date: Mon, 29 Aug 2022 16:23:04 +0200 Subject: [PATCH 13/22] Webtypes: move pathAsAbsolute configuration from global configuration to webtypes configuration --- doc/web-types.md | 1 + src/cli/analyze/analyze-cli-command.ts | 3 +-- src/cli/analyzer-cli-config.ts | 1 - src/cli/cli.ts | 5 ----- src/transformers/transformer-config.ts | 2 +- src/transformers/webtypes/webtypes-transformer.ts | 2 +- 6 files changed, 4 insertions(+), 10 deletions(-) diff --git a/doc/web-types.md b/doc/web-types.md index ade5ae4e..7d3a631a 100644 --- a/doc/web-types.md +++ b/doc/web-types.md @@ -148,5 +148,6 @@ Available parameters: | description-markup | See [http://json.schemastore.org/web-types](http://json.schemastore.org/web-types) description-markup section | | framework-config | See [http://json.schemastore.org/web-types](http://json.schemastore.org/web-types) framework-config section | | default-icon | See [http://json.schemastore.org/web-types](http://json.schemastore.org/web-types) default-icon section | +| path-as-absolute | Consider paths as absolute: don't add './' in front of paths | See [web-types project:](https://github.com/JetBrains/web-types) for more info. diff --git a/src/cli/analyze/analyze-cli-command.ts b/src/cli/analyze/analyze-cli-command.ts index 3ea14d89..0fdd5e06 100644 --- a/src/cli/analyze/analyze-cli-command.ts +++ b/src/cli/analyze/analyze-cli-command.ts @@ -159,8 +159,7 @@ function transformResults(results: AnalyzerResult[] | AnalyzerResult, program: P inlineTypes: config.inlineTypes ?? false, visibility: config.visibility ?? "public", markdown: config.markdown, - cwd: config.cwd, - pathAsAbsolute: config.pathAsAbsolute + cwd: config.cwd }; if (format == "webtypes") { transformerConfig.webTypes = config.parsedWebtypesConfig; diff --git a/src/cli/analyzer-cli-config.ts b/src/cli/analyzer-cli-config.ts index d1bf7ac5..f0a2770d 100644 --- a/src/cli/analyzer-cli-config.ts +++ b/src/cli/analyzer-cli-config.ts @@ -31,7 +31,6 @@ export interface AnalyzerCliConfig { ts?: typeof tsModule; cwd?: string; - pathAsAbsolute?: boolean; webtypesConfig?: string | WebTypesTransformerConfig; parsedWebtypesConfig?: WebTypesTransformerConfig; diff --git a/src/cli/cli.ts b/src/cli/cli.ts index 1e2fc6f2..8607decb 100644 --- a/src/cli/cli.ts +++ b/src/cli/cli.ts @@ -104,11 +104,6 @@ o {tagname}: The element's tag name`, string: true, hidden: true }) - .option("pathAsAbsolute", { - describe: "Consider paths as absolute: don't add './' in front of paths", - boolean: true, - hidden: true - }) .option("webtypesConfig", { describe: "WebTypes header configuration", string: true diff --git a/src/transformers/transformer-config.ts b/src/transformers/transformer-config.ts index 3fac5078..caa23fe4 100644 --- a/src/transformers/transformer-config.ts +++ b/src/transformers/transformer-config.ts @@ -3,7 +3,6 @@ import { GenericContributions } from "./webtypes/webtypes-schema"; export interface TransformerConfig { cwd?: string; - pathAsAbsolute?: boolean; visibility?: VisibilityKind; markdown?: { titleLevel?: number; // deprecated @@ -21,6 +20,7 @@ export interface WebTypesTransformerConfig { framework?: string; "framework-config"?: WebTypesFrameworkConfig; "description-markup"?: "html" | "markdown" | "none"; + pathAsAbsolute?: boolean; } export interface WebTypesFrameworkConfig { diff --git a/src/transformers/webtypes/webtypes-transformer.ts b/src/transformers/webtypes/webtypes-transformer.ts index f538e003..74e6839a 100644 --- a/src/transformers/webtypes/webtypes-transformer.ts +++ b/src/transformers/webtypes/webtypes-transformer.ts @@ -108,7 +108,7 @@ function definitionToHTMLElement(definition: ComponentDefinition, checker: TypeC function getRelativePath(fileName: string | undefined, config: TransformerConfig): string | undefined { return fileName != null && config.cwd != null - ? `${config.pathAsAbsolute ? "" : "./"}${relative(config.cwd, fileName)}`.replaceAll("\\", "/") + ? `${config.webTypes?.pathAsAbsolute ? "" : "./"}${relative(config.cwd, fileName)}`.replaceAll("\\", "/") : undefined; } From 61a3515ef785c84ee42d92d483b30eaf0eb328e8 Mon Sep 17 00:00:00 2001 From: jpradelle Date: Tue, 30 Aug 2022 12:10:20 +0200 Subject: [PATCH 14/22] Webtypes: add tests --- doc/web-types.md | 4 +- src/transformers/webtypes/webtypes-schema.ts | 8 +- test/helpers/webtypes-test-utils.ts | 43 ++++++++ .../attributes-and-properties-test.ts | 103 ++++++++++++++++++ test/transformers/webtypes/basic-test.ts | 74 +++++++++++++ test/transformers/webtypes/css-test.ts | 26 +++++ test/transformers/webtypes/event-test.ts | 29 +++++ 7 files changed, 283 insertions(+), 4 deletions(-) create mode 100644 test/helpers/webtypes-test-utils.ts create mode 100644 test/transformers/webtypes/attributes-and-properties-test.ts create mode 100644 test/transformers/webtypes/basic-test.ts create mode 100644 test/transformers/webtypes/css-test.ts create mode 100644 test/transformers/webtypes/event-test.ts diff --git a/doc/web-types.md b/doc/web-types.md index 7d3a631a..2c6ef9f3 100644 --- a/doc/web-types.md +++ b/doc/web-types.md @@ -73,7 +73,7 @@ Generate your components descriptions with wca ```bash -npm web-types +npm run web-types ``` If your web-types is not named `web-types.json` and placed at root of your project, you need to declare it in your `package.json` @@ -120,7 +120,7 @@ Generate your components descriptions with wca ```bash -npm web-types +npm run web-types ``` If your web-types is not named `web-types.json` and placed at root of your project, you need to declare it in your `package.json` diff --git a/src/transformers/webtypes/webtypes-schema.ts b/src/transformers/webtypes/webtypes-schema.ts index bccddf99..e15db499 100644 --- a/src/transformers/webtypes/webtypes-schema.ts +++ b/src/transformers/webtypes/webtypes-schema.ts @@ -28,7 +28,7 @@ export type Priority = "lowest" | "low" | "normal" | "high" | "highest"; */ export type Icon = string; export type GenericContributions = GenericContributionOrProperty[] | GenericContributionOrProperty; -export type GenericContributionOrProperty = string | number | boolean | GenericContribution | Source; +export type GenericContributionOrProperty = string | number | boolean | object | GenericContribution | Source; export type GenericContribution = TypedContribution; export interface TypedContribution extends BaseContribution { @@ -187,7 +187,11 @@ export interface JsContributionsHost { properties?: GenericJsContribution[]; } -export interface GenericJsContribution extends GenericContribution, JsContributionsHost {} +export interface GenericJsContribution extends GenericContribution, JsContributionsHost { + value?: HtmlAttributeValue; + default?: string; + required?: boolean; +} export type Css = CssContributionsHost; diff --git a/test/helpers/webtypes-test-utils.ts b/test/helpers/webtypes-test-utils.ts new file mode 100644 index 00000000..e383b34d --- /dev/null +++ b/test/helpers/webtypes-test-utils.ts @@ -0,0 +1,43 @@ +import { analyzeTextWithCurrentTsModule } from "./analyze-text-with-current-ts-module"; +import { VirtualSourceFile } from "../../src/analyze"; +import { System } from "typescript"; +import { getCurrentTsModule } from "./ts-test"; +import { transformAnalyzerResult, TransformerConfig, WebTypesTransformerConfig } from "../../src/transformers"; +import { GenericJsContribution, HtmlAttribute, HtmlElement, WebtypesSchema } from "../../src/transformers/webtypes/webtypes-schema"; + +export function runWebtypesBuild(source: VirtualSourceFile[] | VirtualSourceFile, config: Partial = {}): string { + const parseResult = analyzeTextWithCurrentTsModule(source); + + const system: System = getCurrentTsModule().sys; + const transformerConfig: TransformerConfig = { + inlineTypes: false, + visibility: "public", + cwd: system.getCurrentDirectory(), + webTypes: { + name: "test", + version: "1.0.0", + ...config + } + }; + + return transformAnalyzerResult("webtypes", parseResult.results, parseResult.program, transformerConfig); +} + +export function runAndParseWebtypesBuild( + source: VirtualSourceFile[] | VirtualSourceFile, + config: Partial = {} +): WebtypesSchema { + return JSON.parse(runWebtypesBuild(source, config)); +} + +export function findHtmlElementOfName(schema: WebtypesSchema, name: string): HtmlElement | undefined { + return schema.contributions?.html?.elements?.find(el => el.name == name); +} + +export function findAttributeOfName(element: HtmlElement | undefined, name: string): HtmlAttribute | undefined { + return element?.attributes?.find(el => el.name == name); +} + +export function findPropertyOfName(element: HtmlElement | undefined, name: string): GenericJsContribution | undefined { + return element?.js?.properties?.find(el => el.name == name); +} diff --git a/test/transformers/webtypes/attributes-and-properties-test.ts b/test/transformers/webtypes/attributes-and-properties-test.ts new file mode 100644 index 00000000..80aba0a4 --- /dev/null +++ b/test/transformers/webtypes/attributes-and-properties-test.ts @@ -0,0 +1,103 @@ +import { tsTest } from "../../helpers/ts-test"; +import { findAttributeOfName, findHtmlElementOfName, findPropertyOfName, runAndParseWebtypesBuild } from "../../helpers/webtypes-test-utils"; +import { Type } from "../../../src/transformers/webtypes/webtypes-schema"; + +tsTest("Transformer: Webtypes: Attributes and properties only present if needed", t => { + const res = runAndParseWebtypesBuild(` + @customElement('my-element') + class MyElement extends HTMLElement { + @property({type: String, attribute: "my-prop"}) myProp: string; + @property({type: String, attribute: false}) myProp2: string; + } + `); + + const myElement = findHtmlElementOfName(res, "my-element"); + t.truthy(myElement); + t.is(myElement?.attributes?.length, 1); + t.is(myElement?.js?.properties?.length, 2); + t.truthy(findAttributeOfName(myElement, "my-prop")); + t.truthy(findPropertyOfName(myElement, "myProp")); + t.truthy(findPropertyOfName(myElement, "myProp2")); +}); + +tsTest("Transformer: Webtypes: Attributes and properties default value", t => { + const res = runAndParseWebtypesBuild(` + @customElement('my-element') + class MyElement extends HTMLElement { + @property({type: String, attribute: "my-prop"}) myProp: string; + @property({type: String, attribute: "my-prop2"}) myProp2: string = "testValue"; + } + `); + + const myElement = findHtmlElementOfName(res, "my-element"); + t.truthy(myElement); + t.is(myElement?.attributes?.length, 2); + t.is(myElement?.js?.properties?.length, 2); + const att1 = findAttributeOfName(myElement, "my-prop"); + const att2 = findAttributeOfName(myElement, "my-prop2"); + const prop1 = findPropertyOfName(myElement, "myProp"); + const prop2 = findPropertyOfName(myElement, "myProp2"); + + t.is(att1?.value?.type, "string"); + t.false(att1?.required); + t.true(att1?.value?.required); + t.is(att1?.value?.default, undefined); + + t.is(att2?.value?.type, "string"); + t.false(att2?.required); + t.true(att2?.value?.required); + t.is(att2?.value?.default, '"testValue"'); + + t.is(prop1?.value?.type, "string"); + t.false(prop1?.required); + t.true(prop1?.value?.required); + t.is(prop1?.value?.default, undefined); + + t.is(prop2?.value?.type, "string"); + t.false(prop2?.required); + t.true(prop2?.value?.required); + t.is(prop2?.value?.default, '"testValue"'); +}); + +tsTest("Transformer: Webtypes: Boolean value not required", t => { + const res = runAndParseWebtypesBuild(` + @customElement('my-element') + class MyElement extends HTMLElement { + @property({type: Boolean, attribute: "my-prop"}) myProp: boolean; + @property({type: Boolean, attribute: "my-prop2"}) myProp2: boolean | undefined = false; + } + `); + + const myElement = findHtmlElementOfName(res, "my-element"); + t.truthy(myElement); + t.is(myElement?.attributes?.length, 2); + t.is(myElement?.js?.properties?.length, 2); + const att1 = findAttributeOfName(myElement, "my-prop"); + const att2 = findAttributeOfName(myElement, "my-prop2"); + const prop1 = findPropertyOfName(myElement, "myProp"); + const prop2 = findPropertyOfName(myElement, "myProp2"); + + t.is(att1?.value?.type, "boolean"); + t.false(att1?.required); + t.false(att1?.value?.required); + t.is(att1?.value?.default, undefined); + + t.is((att2?.value?.type as Array)?.length, 2); + t.true((att2?.value?.type as Array)?.includes("boolean")); + t.true((att2?.value?.type as Array)?.includes("undefined")); + t.false(att2?.required); + t.false(att2?.value?.required); + t.is(att2?.value?.default, "false"); + + t.is(prop1?.value?.type, "boolean"); + t.false(prop1?.required); + t.false(prop1?.value?.required); + t.is(prop1?.value?.default, undefined); + + t.is((prop2?.value?.type as Array)?.length, 2); + t.true((prop2?.value?.type as Array)?.includes("boolean")); + t.true((prop2?.value?.type as Array)?.includes("undefined")); + t.false(prop2?.required); + t.false(prop2?.value?.required); + t.is(prop2?.value?.default, "false"); +}); diff --git a/test/transformers/webtypes/basic-test.ts b/test/transformers/webtypes/basic-test.ts new file mode 100644 index 00000000..d3cf5d62 --- /dev/null +++ b/test/transformers/webtypes/basic-test.ts @@ -0,0 +1,74 @@ +import { tsTest } from "../../helpers/ts-test"; +import { findAttributeOfName, findHtmlElementOfName, findPropertyOfName, runAndParseWebtypesBuild } from "../../helpers/webtypes-test-utils"; +import { WebTypesTransformerConfig } from "../../../src/transformers"; +import { SourceModule } from "../../../src/transformers/webtypes/webtypes-schema"; + +tsTest("Transformer: Webtypes: File header test", t => { + const config: WebTypesTransformerConfig = { + name: "PkgTest", + version: "1.2.3-test", + "default-icon": "path/foo.png", + "framework-config": { + "enable-when": { + "node-packages": ["lit", "lit-html"] + } + }, + framework: "test-fw", + "description-markup": "html" + }; + const res = runAndParseWebtypesBuild( + ` + @customElement('my-element') + class MyElement extends HTMLElement {} + `, + config + ); + + t.is(res.name, config.name); + t.is(res.version, config.version); + t.is(res.framework, config.framework); + t.is(res["default-icon"], config["default-icon"]); + t.is(res["description-markup"], config["description-markup"]); + t.deepEqual(res["framework-config"], config["framework-config"]); + t.is(res.contributions?.html?.elements?.length, 1); +}); + +tsTest("Transformer: Webtypes: Basic content test", t => { + const res = runAndParseWebtypesBuild([ + ` + @customElement('my-element') + class MyElement extends HTMLElement { + @property({type: String, attribute: "my-prop"}) myProp: string; + @property({type: String, attribute: "my-prop2"}) myProp2: string; + } + `, + ` + @customElement('my-element2') + class MyElement2 extends HTMLElement { + @property({type: String, attribute: "my-prop"}) myProp: string; + } + ` + ]); + + t.is(res.contributions?.html?.elements?.length, 2); + + const myElement = findHtmlElementOfName(res, "my-element"); + t.truthy(myElement); + t.is((myElement?.source as SourceModule)?.symbol, "MyElement"); + // Test element contains expected attributes + t.is(myElement?.attributes?.length, 2); + t.truthy(findAttributeOfName(myElement, "my-prop")); + t.truthy(findAttributeOfName(myElement, "my-prop2")); + // Test element contains expected properties + t.is(myElement?.js?.properties?.length, 2); + t.truthy(findPropertyOfName(myElement, "myProp")); + t.truthy(findPropertyOfName(myElement, "myProp2")); + + const myElement2 = findHtmlElementOfName(res, "my-element2"); + t.truthy(myElement2); + t.is((myElement2?.source as SourceModule)?.symbol, "MyElement2"); + t.is(myElement2?.attributes?.length, 1); + t.truthy(findAttributeOfName(myElement2, "my-prop")); + t.is(myElement2?.js?.properties?.length, 1); + t.truthy(findPropertyOfName(myElement2, "myProp")); +}); diff --git a/test/transformers/webtypes/css-test.ts b/test/transformers/webtypes/css-test.ts new file mode 100644 index 00000000..6d2eb5b7 --- /dev/null +++ b/test/transformers/webtypes/css-test.ts @@ -0,0 +1,26 @@ +import { tsTest } from "../../helpers/ts-test"; +import { runAndParseWebtypesBuild } from "../../helpers/webtypes-test-utils"; + +tsTest("Transformer: Webtypes: CSS properties test", t => { + const res = runAndParseWebtypesBuild(` + /** + * @cssprop [--var-test] - Var test desc + * @cssprop [--var-test2=48px] - Var test desc with default value + */ + @customElement('my-element') + class MyElement extends HTMLElement {} + `); + + const cssProps = res?.contributions?.css?.properties; + t.truthy(cssProps); + t.is(cssProps?.length, 2); + + const varTest = cssProps?.find(cp => cp.name == "--var-test"); + t.truthy(varTest); + t.is(varTest?.description, "Var test desc"); + + const varTest2 = cssProps?.find(cp => cp.name == "--var-test2"); + t.truthy(varTest2); + t.is(varTest2?.description, "Var test desc with default value"); + // default value not put in CSS +}); diff --git a/test/transformers/webtypes/event-test.ts b/test/transformers/webtypes/event-test.ts new file mode 100644 index 00000000..040e8f51 --- /dev/null +++ b/test/transformers/webtypes/event-test.ts @@ -0,0 +1,29 @@ +import { tsTest } from "../../helpers/ts-test"; +import { findHtmlElementOfName, runAndParseWebtypesBuild } from "../../helpers/webtypes-test-utils"; + +tsTest("Transformer: Webtypes: Element events test", t => { + const res = runAndParseWebtypesBuild(` + /** + * @fires test-event - Test event desc + * @fires test-event2 {CustomEvent<{index: int, name: string}>} - Test event desc with typing + */ + @customElement('my-element') + class MyElement extends HTMLElement {} + `); + + const myElement = findHtmlElementOfName(res, "my-element"); + t.truthy(myElement); + + const events = myElement?.js?.events; + t.truthy(events); + t.is(events?.length, 2); + + const testEvent = events?.find(cp => cp.name == "test-event"); + t.truthy(testEvent); + t.is(testEvent?.description, "Test event desc"); + + const testEvent2 = events?.find(cp => cp.name == "test-event2"); + t.truthy(testEvent2); + t.is(testEvent2?.description, "Test event desc with typing"); + // typing value not put in event +}); From 3c2b4c866b8a4095fa21a59feb44ad536eaf4e6a Mon Sep 17 00:00:00 2001 From: jpradelle Date: Tue, 30 Aug 2022 16:18:34 +0200 Subject: [PATCH 15/22] webtypes: add enum support --- .../webtypes/webtypes-transformer.ts | 22 +++++++++++-- .../attributes-and-properties-test.ts | 32 +++++++++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/src/transformers/webtypes/webtypes-transformer.ts b/src/transformers/webtypes/webtypes-transformer.ts index 74e6839a..9308b307 100644 --- a/src/transformers/webtypes/webtypes-transformer.ts +++ b/src/transformers/webtypes/webtypes-transformer.ts @@ -8,7 +8,16 @@ import { ComponentMember } from "../../analyze/types/features/component-member"; import { arrayDefined } from "../../util/array-util"; import { TransformerConfig } from "../transformer-config"; import { TransformerFunction } from "../transformer-function"; -import { HtmlAttribute, WebtypesSchema, HtmlElement, BaseContribution, Js, GenericJsContribution, CssProperty } from "./webtypes-schema"; +import { + HtmlAttribute, + WebtypesSchema, + HtmlElement, + BaseContribution, + Js, + GenericJsContribution, + CssProperty, + HtmlAttributeValue +} from "./webtypes-schema"; import { getFirst } from "../../util/set-util"; import { relative } from "path"; @@ -144,13 +153,22 @@ function componentMemberToAttr( } const types: string[] | string = getTypeHintFromType(member.typeHint ?? member.type?.(), checker, config)?.split(" | ") ?? []; + const isPlainEnum = types.every(t => t == "null" || t == "undefined" || t.trim().match(/^["'].*["']$/)); + const typeValues: Partial = isPlainEnum + ? { + kind: "plain", + type: types.join(" | ") + } + : { + type: types && Array.isArray(types) && types.length == 1 ? types[0] : types + }; const attr: HtmlAttribute = { name: propName, required: !!member.required, priority: member.visibility == "private" || member.visibility == "protected" ? "lowest" : "normal", value: { - type: types && Array.isArray(types) && types.length == 1 ? types[0] : types, + ...typeValues, required: !isBoolean(types), ...(member.default !== undefined ? { default: JSON.stringify(member.default) } : {}) }, diff --git a/test/transformers/webtypes/attributes-and-properties-test.ts b/test/transformers/webtypes/attributes-and-properties-test.ts index 80aba0a4..effb0fba 100644 --- a/test/transformers/webtypes/attributes-and-properties-test.ts +++ b/test/transformers/webtypes/attributes-and-properties-test.ts @@ -101,3 +101,35 @@ tsTest("Transformer: Webtypes: Boolean value not required", t => { t.false(prop2?.value?.required); t.is(prop2?.value?.default, "false"); }); + +tsTest("Transformer: Webtypes: Enum values", t => { + const res = runAndParseWebtypesBuild(` + @customElement('my-element') + class MyElement extends HTMLElement { + /** + * @type {'foo' | 'bar'} + */ + @property({type: String, attribute: "my-prop"}) myProp = "foo"; + } + `); + + const myElement = findHtmlElementOfName(res, "my-element"); + t.truthy(myElement); + + t.is(myElement?.attributes?.length, 1); + t.is(myElement?.js?.properties?.length, 1); + const att1 = findAttributeOfName(myElement, "my-prop"); + const prop1 = findPropertyOfName(myElement, "myProp"); + + t.is(att1?.value?.type, "'foo' | 'bar'"); + t.false(att1?.required); + t.is(att1?.value?.kind, "plain"); + t.true(att1?.value?.required); + t.is(att1?.value?.default, '"foo"'); + + t.is(prop1?.value?.type, "'foo' | 'bar'"); + t.false(prop1?.required); + t.is(prop1?.value?.kind, "plain"); + t.true(prop1?.value?.required); + t.is(prop1?.value?.default, '"foo"'); +}); From 420be56535c4836433a039469bfa72a0f2527dfe Mon Sep 17 00:00:00 2001 From: jpradelle Date: Fri, 2 Sep 2022 15:08:35 +0200 Subject: [PATCH 16/22] webtypes: update documentation to use '@web-types/lit' package for lit web-types definitions --- doc/web-types.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/web-types.md b/doc/web-types.md index 2c6ef9f3..f96d49ed 100644 --- a/doc/web-types.md +++ b/doc/web-types.md @@ -41,11 +41,11 @@ in `webtypesConfig`, this will take package `name` and `version` by default. ### For Lit -Generated web-types are working with the generic `lit-web-types` library. You need to add it on your project. +Generated web-types are working with the generic `@web-types/lit` library. You need to add it on your project. ```bash -npm i lit-web-types -D +npm i @web-types/lit -D ``` Add `wca` section in your `package.json`: From d687d815639dd8620f0d8a2931e77a7c68526c01 Mon Sep 17 00:00:00 2001 From: jpradelle Date: Tue, 20 Sep 2022 16:09:19 +0200 Subject: [PATCH 17/22] Fix README.md --- README.md | 578 +++++++++++++++++++++++++++--------------------------- 1 file changed, 289 insertions(+), 289 deletions(-) diff --git a/README.md b/README.md index 27299e71..d2278e12 100644 --- a/README.md +++ b/README.md @@ -1,289 +1,289 @@ -

web-component-analyzer

- -

- Downloads per month - NPM Version - Dependencies - Contributors -

- -

- Web component analyzer GIF -

- -`web-component-analyzer` is a CLI that makes it possible to easily analyze web components. It analyzes your code and jsdoc in order to extract `properties`, `attributes`, `methods`, `events`, `slots`, `css shadow parts` and `css custom properties`. Works with both javascript and typescript. - -Try the online playground [here](https://runem.github.io/web-component-analyzer/) - -In addition to [vanilla web components](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements) this tool supports web components built with the following libraries: - -- [lit-element](https://github.com/Polymer/lit-element) -- [polymer](https://github.com/Polymer/polymer) -- [stencil](https://github.com/ionic-team/stencil) (partial) -- [lwc](https://github.com/salesforce/lwc) -- [open an issue for library requests](https://github.com/runem/web-component-analyzer/issues) - -[![-----------------------------------------------------](https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/colored.png)](#installation) - -## ➤ Installation - - -```bash -$ npm install -g web-component-analyzer -``` - -**or** - - -```bash -$ npx web-component-analyzer src -``` - -[![-----------------------------------------------------](https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/colored.png)](#usage) - -## ➤ Usage - - -```bash -$ wca analyze -$ wca analyze src --format markdown -$ wca analyze "src/**/*.{js,ts}" --outDir components -$ wca analyze my-element.js --outFile custom-elements.json -$ wca analyze --outFiles {dir}/custom-element.json -``` - - - -The analyze command analyses an optional `` and emits the output to the console as default. When the `` is omitted it will find all components excluding `node_modules`. The default format is `markdown`. - -### Options - - -| Option | Type | Description | -|-----------------------------|--------------------------| ---------------------------------------------------------------------------- | -| `--format ` | `markdown` \ | `json` \| `vscode` \| `webtypes` | Specify output format. Default is `markdown`. | -| `--outDir ` | `directory path` | Direct output to a directory where each file corresponds to a web component. | -| `--outFile ` | `file path` | Concatenate and emit output to a single file. | -| `--outFiles ` | `file path with pattern` | Emit output to multiple files using a pattern. Available substitutions:
**{dir}**: The directory of the component
**{filename}**: The filename (without ext) of the component
**{tagname}**: The element's tag name | -| `--visibility ` | `public` \ | `protected` \| `private` | The mininmum member visibility to output. Default is `public`. | -| `--features ` | `member` \ | `method` \| `cssproperty` \| `csspart` \| `event` \| `slot` | Choose specific features to output. Multiple features are given seperated by a space. All features are enabled as default.
**Example**: `--features member slot event` | -| `--dry` | `boolean` | Don't write any files | - -[![-----------------------------------------------------](https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/colored.png)](#api) - -## ➤ Output Formats - -### json - - -```bash -wca analyze src --format json --outFile custom-elements.json -``` - -Try the online playground [here](https://runem.github.io/web-component-analyzer?format=json) - -This json format is for experimental and demo purposes, and is still being actively discussed. You can expect changes to this format. Please follow and contribute to the discussion at: - -- https://github.com/webcomponents/custom-elements-json -- https://github.com/w3c/webcomponents/issues/776 - -### markdown - - -```bash -wca analyze src --format markdown --outDir readme -``` - -Try the online playground [here](https://runem.github.io/web-component-analyzer?format=markdown) - -Web Component Analyzer can output markdown documentation of your web components. This can either be output into a single file using `--outFile` or into multiple files using `--outDir`. - -### vscode - - -```bash -wca analyze src --format vscode --outFile vscode-html-custom-data.json -``` - -VSCode supports a JSON format called [vscode custom data](https://github.com/microsoft/vscode-custom-data) for the built in html editor which is set using `html.customData` vscode setting. Web Component Analyzer can output this format. - -### webtypes - - -```bash -wca analyze src --format webtypes --outFile web-types-custom.json -``` - -To configure web-types (name, version, etc.) add this section to your `package.json`. `name` and `version` fields are required if wca is not ran from npm `package.json` `scripts` section. -Otherwise it will take `package.json` project `name` and `version`. - -You can use the `wca-config` section to configure other options as well. - -```json -"wca": { - "webtypesConfig": { - "name": "your-project-name", - "version": "0.0.1", - "framework": "lit", - "description-markup": "markdown" - } -} -``` - -Web-types format is a description for IDE completion, see [web-types](https://github.com/JetBrains/web-types/tree/master/packages) - -See [web-component-analyzer web-types dedicated page](./doc/web-types.md) for project setup. - -[![-----------------------------------------------------](https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/colored.png)](#how-does-this-tool-analyze-my-components) - -[![-----------------------------------------------------](https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/colored.png)](#how-to-document-your-components-using-jsdoc) - -## ➤ How to document your components using JSDoc - -In addition to analyzing the code of your components, this library also use JSDoc to construct the documentation. It's especially a good idea to use JSDoc for documenting `slots`, `events`, `css custom properties` and `css shadow parts` as these not analyzed statically by this tool as of now (except when constructing a CustomEvent within your component). - -Here's an example including all supported JSDoc tags. All JSDoc tags are on the the form `@tag {type} name - comment` and `@tag {type} [name=default] - comment`. - - -```javascript -/** - * Here is a description of my web component. - * - * @element my-element - * - * @fires change - This jsdoc tag makes it possible to document events. - * @fires submit - * - * @attr {Boolean} disabled - This jsdoc tag documents an attribute. - * @attr {on|off} switch - Here is an attribute with either the "on" or "off" value. - * @attr [my-attr=default value] - * - * @prop {String} myProp - You can use this jsdoc tag to document properties. - * @prop value - * - * @slot - This is an unnamed slot (the default slot) - * @slot start - This is a slot named "start". - * @slot end - * - * @cssprop --main-bg-color - This jsdoc tag can be used to document css custom properties. - * @cssprop [--main-color=red] - - * @csspart container - */ -class MyElement extends HTMLElement { - - /** - * This is a description of a property with an attribute with exactly the same name: "color". - * @type {"red"|"green"|"blue"} - * @attr - */ - color = "red"; - - /** - * This is a description of a property with an attribute called "my-prop". - * @type {number} - * @deprecated - * @attr my-prop - */ - myProp = 10 - - static get observedAttributes () { - return [ - /** - * The header text of this element - */ - "header" - ]; - } - -} -``` - -### Overview of supported JSDoc tags - - -| JSDoc Tag | Description | -| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | -| `@element` | Gives your component a tag name. This JSDoc tag is useful if your 'customElements.define` is called dynamically eg. using a custom function. | -| `@fires` | Documents events. | -| `@slot` | Documents slots. Using an empty name here documents the unnamed (default) slot. | -| `@attr` or `@attribute` | Documents an attribute on your component. | -| `@prop` or `@property` | Documents a property on your component. | -| `@cssprop` or `@cssproperty` | Documents a css custom property on your component. | -| `@csspart` | Documents a css shadow part on your component. | - -[![-----------------------------------------------------](https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/colored.png)](#contributors) - -## ➤ How does this tool analyze my components? - -This tool extract information about your components by looking at your code directly and by looking at your JSDoc comments. - -**Code**: Web Component Analyzer supports multiple libraries. [Click here](https://github.com/runem/web-component-analyzer/blob/master/ANALYZE.md) for an overview of how each library is analyzed. - -**JSDoc**: Read next section to learn more about how JSDoc is analyzed. - -## ➤ API - -You can also directly use the underlying functionality of this tool if you don't want to use the CLI. Web Component Analyzer analyzes Typescript source files, so you will have to include the Typescript parser. Here are some examples of how to use the API. - -### Analyze Typescript source file - - -```typescript -import { analyzeSourceFile } from "web-component-analyzer"; - -const result = analyzeSourceFile(sourceFile, { checker }); -``` - -### Analyze text - - -```javascript -import { analyzeText } from "web-component-analyzer"; - -const code = `class MyElement extends HTMLElement { - -} - -customElements.define("my-element", MyElement); -`; - - -const { results, program } = analyzeText(code); -// or -const { results, program } = analyzeText([ - { fileName: "file1.js", text: code }, - { fileName: "file2.js", text: "..." }, // these files can depend on each other - { fileName: "file3.js", text: "...", analyze: false } -]); -// each result in "results" is the result of analyzing the corresponding text where "analyze" is not false -``` - -### Transform the result - - -```javascript -import { transformAnalyzerResult } from "web-component-analyzer"; - -const result = // the result of analyzing the component using one of the above functions - -const format = "markdown"; // or "json" - -const output = transformAnalyzerResult(format, result, program); - -// "output" is now a string containing the result of the "markdown" transformer -``` - -[![-----------------------------------------------------](https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/colored.png)](#how-does-this-tool-analyze-my-components) - -## ➤ Contributors - -| [Rune Mehlsen](https://github.com/runem) | -| :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | -| [Rune Mehlsen](https://github.com/runem) | - -[![-----------------------------------------------------](https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/colored.png)](#license) - -## ➤ License - -Licensed under [MIT](https://opensource.org/licenses/MIT). +

web-component-analyzer

+ +

+ Downloads per month + NPM Version + Dependencies + Contributors +

+ +

+ Web component analyzer GIF +

+ +`web-component-analyzer` is a CLI that makes it possible to easily analyze web components. It analyzes your code and jsdoc in order to extract `properties`, `attributes`, `methods`, `events`, `slots`, `css shadow parts` and `css custom properties`. Works with both javascript and typescript. + +Try the online playground [here](https://runem.github.io/web-component-analyzer/) + +In addition to [vanilla web components](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements) this tool supports web components built with the following libraries: + +- [lit-element](https://github.com/Polymer/lit-element) +- [polymer](https://github.com/Polymer/polymer) +- [stencil](https://github.com/ionic-team/stencil) (partial) +- [lwc](https://github.com/salesforce/lwc) +- [open an issue for library requests](https://github.com/runem/web-component-analyzer/issues) + +[![-----------------------------------------------------](https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/colored.png)](#installation) + +## ➤ Installation + + +```bash +$ npm install -g web-component-analyzer +``` + +**or** + + +```bash +$ npx web-component-analyzer src +``` + +[![-----------------------------------------------------](https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/colored.png)](#usage) + +## ➤ Usage + + +```bash +$ wca analyze +$ wca analyze src --format markdown +$ wca analyze "src/**/*.{js,ts}" --outDir components +$ wca analyze my-element.js --outFile custom-elements.json +$ wca analyze --outFiles {dir}/custom-element.json +``` + + + +The analyze command analyses an optional `` and emits the output to the console as default. When the `` is omitted it will find all components excluding `node_modules`. The default format is `markdown`. + +### Options + + +| Option | Type | Description | +| --------------------------- | -------------------------------- | ---------------------------------------------------------------------------- | +| `--format ` | `markdown` \| `json` \| `vscode` \| `webtypes` | Specify output format. Default is `markdown`. | +| `--outDir ` | `directory path` | Direct output to a directory where each file corresponds to a web component. | +| `--outFile ` | `file path` | Concatenate and emit output to a single file. | +| `--outFiles ` | `file path with pattern` | Emit output to multiple files using a pattern. Available substitutions:
**{dir}**: The directory of the component
**{filename}**: The filename (without ext) of the component
**{tagname}**: The element's tag name | +| `--visibility ` | `public` \| `protected` \| `private` | The mininmum member visibility to output. Default is `public`. | +| `--features ` | `member` \| `method` \| `cssproperty` \| `csspart` \| `event` \| `slot` | Choose specific features to output. Multiple features are given seperated by a space. All features are enabled as default.
**Example**: `--features member slot event` | +| `--dry` | `boolean` | Don't write any files | + +[![-----------------------------------------------------](https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/colored.png)](#api) + +## ➤ Output Formats + +### json + + +```bash +wca analyze src --format json --outFile custom-elements.json +``` + +Try the online playground [here](https://runem.github.io/web-component-analyzer?format=json) + +This json format is for experimental and demo purposes, and is still being actively discussed. You can expect changes to this format. Please follow and contribute to the discussion at: + +- https://github.com/webcomponents/custom-elements-json +- https://github.com/w3c/webcomponents/issues/776 + +### markdown + + +```bash +wca analyze src --format markdown --outDir readme +``` + +Try the online playground [here](https://runem.github.io/web-component-analyzer?format=markdown) + +Web Component Analyzer can output markdown documentation of your web components. This can either be output into a single file using `--outFile` or into multiple files using `--outDir`. + +### vscode + + +```bash +wca analyze src --format vscode --outFile vscode-html-custom-data.json +``` + +VSCode supports a JSON format called [vscode custom data](https://github.com/microsoft/vscode-custom-data) for the built in html editor which is set using `html.customData` vscode setting. Web Component Analyzer can output this format. + +### webtypes + + +```bash +wca analyze src --format webtypes --outFile web-types-custom.json +``` + +To configure web-types (name, version, etc.) add this section to your `package.json`. `name` and `version` fields are required if wca is not ran from npm `package.json` `scripts` section. +Otherwise it will take `package.json` project `name` and `version`. + +You can use the `wca-config` section to configure other options as well. + +```json +"wca": { + "webtypesConfig": { + "name": "your-project-name", + "version": "0.0.1", + "framework": "lit", + "description-markup": "markdown" + } +} +``` + +Web-types format is a description for IDE completion, see [web-types](https://github.com/JetBrains/web-types/tree/master/packages) + +See [web-component-analyzer web-types dedicated page](./doc/web-types.md) for project setup. + +[![-----------------------------------------------------](https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/colored.png)](#how-does-this-tool-analyze-my-components) + +[![-----------------------------------------------------](https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/colored.png)](#how-to-document-your-components-using-jsdoc) + +## ➤ How to document your components using JSDoc + +In addition to analyzing the code of your components, this library also use JSDoc to construct the documentation. It's especially a good idea to use JSDoc for documenting `slots`, `events`, `css custom properties` and `css shadow parts` as these not analyzed statically by this tool as of now (except when constructing a CustomEvent within your component). + +Here's an example including all supported JSDoc tags. All JSDoc tags are on the the form `@tag {type} name - comment` and `@tag {type} [name=default] - comment`. + + +```javascript +/** + * Here is a description of my web component. + * + * @element my-element + * + * @fires change - This jsdoc tag makes it possible to document events. + * @fires submit + * + * @attr {Boolean} disabled - This jsdoc tag documents an attribute. + * @attr {on|off} switch - Here is an attribute with either the "on" or "off" value. + * @attr [my-attr=default value] + * + * @prop {String} myProp - You can use this jsdoc tag to document properties. + * @prop value + * + * @slot - This is an unnamed slot (the default slot) + * @slot start - This is a slot named "start". + * @slot end + * + * @cssprop --main-bg-color - This jsdoc tag can be used to document css custom properties. + * @cssprop [--main-color=red] + + * @csspart container + */ +class MyElement extends HTMLElement { + + /** + * This is a description of a property with an attribute with exactly the same name: "color". + * @type {"red"|"green"|"blue"} + * @attr + */ + color = "red"; + + /** + * This is a description of a property with an attribute called "my-prop". + * @type {number} + * @deprecated + * @attr my-prop + */ + myProp = 10 + + static get observedAttributes () { + return [ + /** + * The header text of this element + */ + "header" + ]; + } + +} +``` + +### Overview of supported JSDoc tags + + +| JSDoc Tag | Description | +| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | +| `@element` | Gives your component a tag name. This JSDoc tag is useful if your 'customElements.define` is called dynamically eg. using a custom function. | +| `@fires` | Documents events. | +| `@slot` | Documents slots. Using an empty name here documents the unnamed (default) slot. | +| `@attr` or `@attribute` | Documents an attribute on your component. | +| `@prop` or `@property` | Documents a property on your component. | +| `@cssprop` or `@cssproperty` | Documents a css custom property on your component. | +| `@csspart` | Documents a css shadow part on your component. | + +[![-----------------------------------------------------](https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/colored.png)](#contributors) + +## ➤ How does this tool analyze my components? + +This tool extract information about your components by looking at your code directly and by looking at your JSDoc comments. + +**Code**: Web Component Analyzer supports multiple libraries. [Click here](https://github.com/runem/web-component-analyzer/blob/master/ANALYZE.md) for an overview of how each library is analyzed. + +**JSDoc**: Read next section to learn more about how JSDoc is analyzed. + +## ➤ API + +You can also directly use the underlying functionality of this tool if you don't want to use the CLI. Web Component Analyzer analyzes Typescript source files, so you will have to include the Typescript parser. Here are some examples of how to use the API. + +### Analyze Typescript source file + + +```typescript +import { analyzeSourceFile } from "web-component-analyzer"; + +const result = analyzeSourceFile(sourceFile, { checker }); +``` + +### Analyze text + + +```javascript +import { analyzeText } from "web-component-analyzer"; + +const code = `class MyElement extends HTMLElement { + +} + +customElements.define("my-element", MyElement); +`; + + +const { results, program } = analyzeText(code); +// or +const { results, program } = analyzeText([ + { fileName: "file1.js", text: code }, + { fileName: "file2.js", text: "..." }, // these files can depend on each other + { fileName: "file3.js", text: "...", analyze: false } +]); +// each result in "results" is the result of analyzing the corresponding text where "analyze" is not false +``` + +### Transform the result + + +```javascript +import { transformAnalyzerResult } from "web-component-analyzer"; + +const result = // the result of analyzing the component using one of the above functions + +const format = "markdown"; // or "json" + +const output = transformAnalyzerResult(format, result, program); + +// "output" is now a string containing the result of the "markdown" transformer +``` + +[![-----------------------------------------------------](https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/colored.png)](#how-does-this-tool-analyze-my-components) + +## ➤ Contributors + +| [Rune Mehlsen](https://github.com/runem) | +| :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| [Rune Mehlsen](https://github.com/runem) | + +[![-----------------------------------------------------](https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/colored.png)](#license) + +## ➤ License + +Licensed under [MIT](https://opensource.org/licenses/MIT). From 3480a94c66a5bb91da0212c50ecd5fb5bd8e9a92 Mon Sep 17 00:00:00 2001 From: jpradelle Date: Wed, 12 Apr 2023 14:15:10 +0200 Subject: [PATCH 18/22] Webtypes: add source field for component properties and attributes --- .../webtypes/webtypes-transformer.ts | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/src/transformers/webtypes/webtypes-transformer.ts b/src/transformers/webtypes/webtypes-transformer.ts index 9308b307..dac4ddcf 100644 --- a/src/transformers/webtypes/webtypes-transformer.ts +++ b/src/transformers/webtypes/webtypes-transformer.ts @@ -21,6 +21,11 @@ import { import { getFirst } from "../../util/set-util"; import { relative } from "path"; +interface SourceDescription { + module: string; + className: string; +} + /** * Transforms results to json. * @param results @@ -89,21 +94,30 @@ function definitionToHTMLElement(definition: ComponentDefinition, checker: TypeC const node = getFirst(definition.identifierNodes); const path = getRelativePath(node?.getSourceFile().fileName, config); + let sourceDescription: SourceDescription | null = null; if (node?.getText() && path) { build.source = { module: path, symbol: node.getText() }; + sourceDescription = { + module: path, + className: node.getText() + }; } // Build attributes - const customElementAttributes = arrayDefined(declaration.members.map(d => componentMemberToAttr(d.attrName, true, d, checker, config))); + const customElementAttributes = arrayDefined( + declaration.members.map(d => componentMemberToAttr(d.attrName, d, sourceDescription, checker, config)) + ); if (customElementAttributes.length > 0) build.attributes = customElementAttributes; const js: Js = {}; // Build properties - const customElementProperties = arrayDefined(declaration.members.map(d => componentMemberToAttr(d.propName, false, d, checker, config))); + const customElementProperties = arrayDefined( + declaration.members.map(d => componentMemberToAttr(d.propName, d, sourceDescription, checker, config)) + ); if (customElementProperties.length > 0) js.properties = customElementProperties; // Build events @@ -143,8 +157,8 @@ function componentCssPropertiesToAttr(cssProperty: ComponentCssProperty): CssPro function componentMemberToAttr( propName: string | undefined, - isAttribute: boolean, member: ComponentMember, + sourceDescription: SourceDescription | null, checker: TypeChecker, config: TransformerConfig ): BaseContribution | undefined { @@ -177,6 +191,13 @@ function componentMemberToAttr( if (member?.jsDoc?.description) attr.description = member.jsDoc.description; + if (sourceDescription !== null) { + attr.source = { + module: sourceDescription.module, + symbol: sourceDescription.className + "." + member.propName + }; + } + return attr; } From 67cd427c9d16e09eec6ecd55e373b6bf88418afc Mon Sep 17 00:00:00 2001 From: jpradelle Date: Thu, 19 Oct 2023 14:23:15 +0200 Subject: [PATCH 19/22] webtypes: add slots support --- src/transformers/webtypes/webtypes-schema.ts | 3 +++ .../webtypes/webtypes-transformer.ts | 15 ++++++++++++- .../attributes-and-properties-test.ts | 21 +++++++++++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/transformers/webtypes/webtypes-schema.ts b/src/transformers/webtypes/webtypes-schema.ts index e15db499..5f968857 100644 --- a/src/transformers/webtypes/webtypes-schema.ts +++ b/src/transformers/webtypes/webtypes-schema.ts @@ -147,6 +147,7 @@ export interface BaseContribution { export interface HtmlContributionHost { elements?: HtmlElement[]; attributes?: HtmlAttribute[]; + slots?: SlotAttribute[]; } export interface HtmlAttribute extends BaseContribution, HtmlContributionHost { @@ -162,6 +163,8 @@ export interface HtmlAttributeValue { kind?: HtmlAttributeType; } +export interface SlotAttribute extends BaseContribution {} + export interface TypeReference { module?: string; name: string; diff --git a/src/transformers/webtypes/webtypes-transformer.ts b/src/transformers/webtypes/webtypes-transformer.ts index dac4ddcf..6bfcd988 100644 --- a/src/transformers/webtypes/webtypes-transformer.ts +++ b/src/transformers/webtypes/webtypes-transformer.ts @@ -16,7 +16,8 @@ import { Js, GenericJsContribution, CssProperty, - HtmlAttributeValue + HtmlAttributeValue, + SlotAttribute } from "./webtypes-schema"; import { getFirst } from "../../util/set-util"; import { relative } from "path"; @@ -126,6 +127,18 @@ function definitionToHTMLElement(definition: ComponentDefinition, checker: TypeC if (js.properties || js.events) build.js = js; + // Build slots + const slots: SlotAttribute[] = declaration.slots?.map(slot => ({ + name: slot.name || "", + description: slot.jsDoc?.description || "" + })); + if (slots && slots.length > 0) { + slots.forEach(slot => { + if (slot.name == "") slot.priority = "low"; + }); + build.slots = slots; + } + return build; } diff --git a/test/transformers/webtypes/attributes-and-properties-test.ts b/test/transformers/webtypes/attributes-and-properties-test.ts index effb0fba..b675344b 100644 --- a/test/transformers/webtypes/attributes-and-properties-test.ts +++ b/test/transformers/webtypes/attributes-and-properties-test.ts @@ -133,3 +133,24 @@ tsTest("Transformer: Webtypes: Enum values", t => { t.true(prop1?.value?.required); t.is(prop1?.value?.default, '"foo"'); }); + +tsTest("Transformer: Webtypes: Slots values", t => { + const res = runAndParseWebtypesBuild(` + /** + * @slot - Default slot desc + * @slot named-slot - Named slot desc + */ + @customElement('my-element') + class MyElement extends HTMLElement {} + `); + + const myElement = findHtmlElementOfName(res, "my-element"); + t.truthy(myElement); + + t.is(myElement?.slots?.length, 2); + const defaultSlot = myElement?.slots?.find(slot => slot.name == ""); + const namedSlot = myElement?.slots?.find(slot => slot.name == "named-slot"); + + t.is(defaultSlot?.description, "Default slot desc"); + t.is(namedSlot?.description, "Named slot desc"); +}); From d483b1afe5c2a769b7c7ad0c08bd9b5c6a4e6f67 Mon Sep 17 00:00:00 2001 From: jpradelle Date: Thu, 19 Oct 2023 14:39:12 +0200 Subject: [PATCH 20/22] webtypes: add css parts support --- src/transformers/webtypes/webtypes-schema.ts | 3 +++ .../webtypes/webtypes-transformer.ts | 14 ++++++++++++- .../attributes-and-properties-test.ts | 21 +++++++++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/src/transformers/webtypes/webtypes-schema.ts b/src/transformers/webtypes/webtypes-schema.ts index 5f968857..3e8adc18 100644 --- a/src/transformers/webtypes/webtypes-schema.ts +++ b/src/transformers/webtypes/webtypes-schema.ts @@ -165,6 +165,8 @@ export interface HtmlAttributeValue { export interface SlotAttribute extends BaseContribution {} +export interface CssPartAttribute extends BaseContribution {} + export interface TypeReference { module?: string; name: string; @@ -204,6 +206,7 @@ export interface CssContributionsHost { "pseudo-classes"?: CssPseudoClass[]; functions?: CssGenericItem[]; classes?: CssGenericItem[]; + parts?: CssPartAttribute[]; } export interface CssProperty extends BaseContribution, CssContributionsHost { diff --git a/src/transformers/webtypes/webtypes-transformer.ts b/src/transformers/webtypes/webtypes-transformer.ts index 6bfcd988..c7f690c8 100644 --- a/src/transformers/webtypes/webtypes-transformer.ts +++ b/src/transformers/webtypes/webtypes-transformer.ts @@ -17,7 +17,8 @@ import { GenericJsContribution, CssProperty, HtmlAttributeValue, - SlotAttribute + SlotAttribute, + CssContributionsHost } from "./webtypes-schema"; import { getFirst } from "../../util/set-util"; import { relative } from "path"; @@ -139,6 +140,17 @@ function definitionToHTMLElement(definition: ComponentDefinition, checker: TypeC build.slots = slots; } + // Build CSS + const css: CssContributionsHost = {}; + if (declaration.cssParts && declaration.cssParts.length > 0) { + css.parts = declaration.cssParts?.map(part => ({ + name: part.name, + description: part.jsDoc?.description || "" + })); + } + + if (css.parts) build.css = css; + return build; } diff --git a/test/transformers/webtypes/attributes-and-properties-test.ts b/test/transformers/webtypes/attributes-and-properties-test.ts index b675344b..5c266ce5 100644 --- a/test/transformers/webtypes/attributes-and-properties-test.ts +++ b/test/transformers/webtypes/attributes-and-properties-test.ts @@ -154,3 +154,24 @@ tsTest("Transformer: Webtypes: Slots values", t => { t.is(defaultSlot?.description, "Default slot desc"); t.is(namedSlot?.description, "Named slot desc"); }); + +tsTest("Transformer: Webtypes: CssParts values", t => { + const res = runAndParseWebtypesBuild(` + /** + * @csspart part1 - Part 1 desc + * @csspart part-2 - Part 2 desc + */ + @customElement('my-element') + class MyElement extends HTMLElement {} + `); + + const myElement = findHtmlElementOfName(res, "my-element"); + t.truthy(myElement); + + t.is(myElement?.css?.parts?.length, 2); + const defaultSlot = myElement?.css?.parts?.find(slot => slot.name == "part1"); + const namedSlot = myElement?.css?.parts?.find(slot => slot.name == "part-2"); + + t.is(defaultSlot?.description, "Part 1 desc"); + t.is(namedSlot?.description, "Part 2 desc"); +}); From ae6c3028817e349d60344ecb3042c007e0de7122 Mon Sep 17 00:00:00 2001 From: jpradelle Date: Fri, 20 Oct 2023 17:11:21 +0200 Subject: [PATCH 21/22] Webtypes: add default value of CSS properties in description --- src/transformers/webtypes/webtypes-transformer.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/transformers/webtypes/webtypes-transformer.ts b/src/transformers/webtypes/webtypes-transformer.ts index c7f690c8..3e57a561 100644 --- a/src/transformers/webtypes/webtypes-transformer.ts +++ b/src/transformers/webtypes/webtypes-transformer.ts @@ -140,7 +140,7 @@ function definitionToHTMLElement(definition: ComponentDefinition, checker: TypeC build.slots = slots; } - // Build CSS + // Build component CSS const css: CssContributionsHost = {}; if (declaration.cssParts && declaration.cssParts.length > 0) { css.parts = declaration.cssParts?.map(part => ({ @@ -176,6 +176,10 @@ function componentCssPropertiesToAttr(cssProperty: ComponentCssProperty): CssPro }; if (cssProperty?.jsDoc?.description) builtCssProp.description = cssProperty.jsDoc.description; + if (cssProperty.default) { + if (builtCssProp.description) builtCssProp.description += "\n\n**Default:** " + cssProperty.default; + else builtCssProp.description = "**Default:** " + cssProperty.default; + } return builtCssProp; } From 1a8310241de1c07f5b43c15fcc3713da92c2491c Mon Sep 17 00:00:00 2001 From: jpradelle Date: Wed, 25 Oct 2023 10:02:20 +0200 Subject: [PATCH 22/22] webtypes: fix CSS test --- test/transformers/webtypes/css-test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/transformers/webtypes/css-test.ts b/test/transformers/webtypes/css-test.ts index 6d2eb5b7..de13afcc 100644 --- a/test/transformers/webtypes/css-test.ts +++ b/test/transformers/webtypes/css-test.ts @@ -21,6 +21,6 @@ tsTest("Transformer: Webtypes: CSS properties test", t => { const varTest2 = cssProps?.find(cp => cp.name == "--var-test2"); t.truthy(varTest2); - t.is(varTest2?.description, "Var test desc with default value"); + t.is(varTest2?.description, "Var test desc with default value\n\n**Default:** 48px"); // default value not put in CSS });