Skip to content

Commit

Permalink
Add support for css shadow parts. This closes #109
Browse files Browse the repository at this point in the history
  • Loading branch information
runem committed Nov 2, 2019
1 parent cff25b0 commit 366d651
Show file tree
Hide file tree
Showing 28 changed files with 270 additions and 11 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ Here's an example including all supported JSDoc tags. All tags are on the the fo
*
* @cssprop --main-bg-color - This jsdoc tag can be used to document css custom properties.
* @cssprop --main-color
* @csspart container
*/
class MyElement extends HTMLElement {

Expand Down Expand Up @@ -156,6 +158,7 @@ class MyElement extends HTMLElement {
| `@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)

Expand Down
8 changes: 6 additions & 2 deletions blueprint.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
{
"supported_jsdocs": [
["JSDoc Tag", "Description"],
["`@element`", "Gives your component a tag name. This JSDoc tag is especially useful if your 'customElements.define` is called dynamically eg. using a custom function."],
[
"`@element`",
"Gives your component a tag name. This JSDoc tag is especially 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."]
["`@cssprop` or `@cssproperty`", "Documents a css custom property on your component."],
["`@csspart`", "Documents a css shadow part on your component."]
],
"usage_analyze_options": [
["Option", "Type", "Description"],
Expand Down
1 change: 1 addition & 0 deletions dev/src/custom-element/custom-element.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/**
* @fires my-custom-event - Im event!
* @csspart mypart - Hello
*/
export class CustomElement extends HTMLElement {
myProp = "hello";
Expand Down
6 changes: 6 additions & 0 deletions src/analyze/flavors/js-doc/js-doc-flavor.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { Node } from "typescript";
import { ComponentCSSPart } from "../../types/component-css-part";
import { ComponentCSSProperty } from "../../types/component-css-property";
import { ComponentMember } from "../../types/component-member";
import { ComponentSlot } from "../../types/component-slot";
import { EventDeclaration } from "../../types/event-types";
import { ParseComponentFlavor, ParseComponentMembersContext, VisitComponentDefinitionContext } from "../parse-component-flavor";
import { parseDeclarationCSSProps } from "./parse-declaration-css-props";
import { parseDeclarationCSSParts } from "./parse-declaration-css-parts";
import { parseDeclarationEvents } from "./parse-declaration-events";
import { parseDeclarationMembers } from "./parse-declaration-members";
import { parseDeclarationSlots } from "./parse-declaration-slots";
Expand Down Expand Up @@ -34,4 +36,8 @@ export class JsDocFlavor implements ParseComponentFlavor {
parseDeclarationCSSProps(node: Node, context: ParseComponentMembersContext): ComponentCSSProperty[] | undefined {
return parseDeclarationCSSProps(node, context);
}

parseDeclarationCSSParts(node: Node, context: ParseComponentMembersContext): ComponentCSSPart[] | undefined {
return parseDeclarationCSSParts(node, context);
}
}
31 changes: 31 additions & 0 deletions src/analyze/flavors/js-doc/parse-declaration-css-parts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Node } from "typescript";
import { ComponentCSSPart } from "../../types/component-css-part";
import { ParseComponentMembersContext } from "../parse-component-flavor";
import { parseJsDocForNode } from "./helper";

/**
* Parses @csspart jsdoc annotations on interface/class-like nodes.
* @param node
* @param context
*/
export function parseDeclarationCSSParts(node: Node, context: ParseComponentMembersContext): ComponentCSSPart[] | undefined {
const { ts } = context;

if (ts.isInterfaceDeclaration(node) || ts.isClassDeclaration(node)) {
return parseJsDocForNode(
node,
["csspart"],
(tagNode, parsed) => {
if (parsed.name != null) {
return {
name: parsed.name,
jsDoc: parsed.comment != null ? { comment: parsed.comment } : undefined
} as ComponentCSSPart;
}
},
context
);
}

return undefined;
}
3 changes: 3 additions & 0 deletions src/analyze/flavors/parse-component-flavor.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as tsModule from "typescript";
import { Node, TypeChecker } from "typescript";
import { AnalyzeComponentsConfig } from "../analyze-components";
import { ComponentCSSPart } from "../types/component-css-part";
import { ComponentCSSProperty } from "../types/component-css-property";
import { ComponentDiagnostic } from "../types/component-diagnostic";
import { ComponentMember } from "../types/component-member";
Expand All @@ -20,6 +21,7 @@ export interface FlavorVisitContextFeatures {
getMembers(): ComponentMember[];
getSlots(): ComponentSlot[];
getCSSProps(): ComponentCSSProperty[];
getCSSParts(): ComponentCSSPart[];
getEvents(): EventDeclaration[];
getInheritNodes(): Node[];
getInherits(): string[];
Expand Down Expand Up @@ -51,6 +53,7 @@ export interface ParseComponentFlavor {
parseDeclarationEvents?(node: Node, context: ParseComponentMembersContext): EventDeclaration[] | undefined;
parseDeclarationSlots?(node: Node, context: ParseComponentMembersContext): ComponentSlot[] | undefined;
parseDeclarationCSSProps?(node: Node, context: ParseComponentMembersContext): ComponentCSSProperty[] | undefined;
parseDeclarationCSSParts?(node: Node, context: ParseComponentMembersContext): ComponentCSSPart[] | undefined;

visitGlobalEvents?(node: Node, context: ParseVisitContextGlobalEvents): void;
}
9 changes: 9 additions & 0 deletions src/analyze/parse/merge-declarations.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { isAssignableToSimpleTypeKind, isSimpleType, SimpleType, SimpleTypeKind, toSimpleType } from "ts-simple-type";
import { Type, TypeChecker } from "typescript";
import { FlavorVisitContext } from "../flavors/parse-component-flavor";
import { ComponentCSSPart } from "../types/component-css-part";
import { ComponentCSSProperty } from "../types/component-css-property";
import { ComponentDeclaration } from "../types/component-declaration";
import { ComponentMember, ComponentMemberAttribute, ComponentMemberProperty } from "../types/component-member";
Expand Down Expand Up @@ -65,6 +66,14 @@ export function mergeCSSProps(cssProps: ComponentCSSProperty[]): ComponentCSSPro
return nameMerge(cssProps, "last");
}

/**
* Merges css parts
* @param cssParts
*/
export function mergeCSSParts(cssParts: ComponentCSSPart[]): ComponentCSSPart[] {
return nameMerge(cssParts, "last");
}

/**
* Merges based on a name and a direction.
* "first": Only keep the first found item.
Expand Down
23 changes: 21 additions & 2 deletions src/analyze/parse/parse-declaration.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ClassLikeDeclaration, InterfaceDeclaration, Node } from "typescript";
import { FlavorVisitContext, ParseComponentFlavor } from "../flavors/parse-component-flavor";
import { ComponentCSSPart } from "../types/component-css-part";
import { ComponentCSSProperty } from "../types/component-css-property";
import { ComponentDeclaration } from "../types/component-declaration";
import { ComponentMember } from "../types/component-member";
Expand All @@ -8,14 +9,15 @@ import { EventDeclaration } from "../types/event-types";
import { findChild, isNodeInLibDom, resolveDeclarations } from "../util/ast-util";
import { getJsDoc } from "../util/js-doc-util";
import { expandMembersFromJsDoc } from "./expand-from-js-doc";
import { mergeCSSProps, mergeEvents, mergeMembers, mergeSlots } from "./merge-declarations";
import { mergeCSSParts, mergeCSSProps, mergeEvents, mergeMembers, mergeSlots } from "./merge-declarations";
import { mergeJsDocs } from "./merge-js-docs";

interface VisitComponentDeclarationVisitContext extends FlavorVisitContext {
declarationNode: Node;
emitMembers(members: ComponentMember[]): void;
emitSlots(slots: ComponentSlot[]): void;
emitCSSProps(cssProperties: ComponentCSSProperty[]): void;
emitCSSParts(cssParts: ComponentCSSPart[]): void;
emitEvents(events: EventDeclaration[]): void;
emitInheritNode(node: Node): void;
emitInherit(name: string): void;
Expand All @@ -32,6 +34,7 @@ export function parseComponentDeclaration(declarationNode: Node, flavors: ParseC
const members: ComponentMember[] = [];
const events: EventDeclaration[] = [];
const cssProps: ComponentCSSProperty[] = [];
const cssParts: ComponentCSSPart[] = [];
const inherits = new Set<string>();
const inheritNodes = new Set<Node>();

Expand All @@ -43,6 +46,9 @@ export function parseComponentDeclaration(declarationNode: Node, flavors: ParseC
getCSSProps(): ComponentCSSProperty[] {
return cssProps;
},
getCSSParts(): ComponentCSSPart[] {
return cssParts;
},
getEvents(): EventDeclaration[] {
return events;
},
Expand All @@ -68,6 +74,9 @@ export function parseComponentDeclaration(declarationNode: Node, flavors: ParseC
emitCSSProps(newCSSProps: ComponentCSSProperty[]): void {
cssProps.push(...newCSSProps);
},
emitCSSParts(newCSSParts: ComponentCSSPart[]): void {
cssParts.push(...newCSSParts);
},
emitEvents(newEvents: EventDeclaration[]): void {
events.push(...newEvents);
},
Expand All @@ -93,6 +102,7 @@ export function parseComponentDeclaration(declarationNode: Node, flavors: ParseC
const mergedSlots = mergeSlots(slots);
const mergedEvents = mergeEvents(events);
const mergedCSSProps = mergeCSSProps(cssProps);
const mergedCSSParts = mergeCSSParts(cssParts);

const className =
(context.ts.isClassDeclaration(declarationNode) || context.ts.isInterfaceDeclaration(declarationNode)) && declarationNode.name != null
Expand All @@ -105,6 +115,7 @@ export function parseComponentDeclaration(declarationNode: Node, flavors: ParseC
slots: mergedSlots,
events: mergedEvents,
cssProperties: mergedCSSProps,
cssParts: mergedCSSParts,
inheritNodes: Array.from(inheritNodes.values()),
inherits: Array.from(inherits.values()),
className,
Expand Down Expand Up @@ -173,6 +184,13 @@ function visitComponentDeclaration(node: Node, flavors: ParseComponentFlavor[],
if (!cssPropertiesResult.shouldContinue) return;
}

// Emit css parts
const cssPartsResult = executeFirstFlavor(flavors, "parseDeclarationCSSParts", node, context);
if (cssPartsResult != null) {
context.emitCSSParts(cssPartsResult.result);
if (!cssPartsResult.shouldContinue) return;
}

// Emit slots
const slotsResult = executeFirstFlavor(flavors, "parseDeclarationSlots", node, context);
if (slotsResult != null) {
Expand All @@ -199,7 +217,8 @@ function executeFirstFlavor<
| keyof ParseComponentFlavor & "parseDeclarationMembers"
| "parseDeclarationEvents"
| "parseDeclarationSlots"
| "parseDeclarationCSSProps",
| "parseDeclarationCSSProps"
| "parseDeclarationCSSParts",
Return extends ReturnType<NonNullable<ParseComponentFlavor[Key]>>
>(
flavors: ParseComponentFlavor[],
Expand Down
6 changes: 6 additions & 0 deletions src/analyze/types/component-css-part.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { JsDoc } from "./js-doc";

export interface ComponentCSSPart {
name: string;
jsDoc?: JsDoc;
}
2 changes: 2 additions & 0 deletions src/analyze/types/component-declaration.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Node } from "typescript";
import { ComponentCSSPart } from "./component-css-part";
import { ComponentCSSProperty } from "./component-css-property";
import { ComponentMember } from "./component-member";
import { ComponentSlot } from "./component-slot";
Expand All @@ -13,6 +14,7 @@ export interface ComponentDeclaration {
events: EventDeclaration[];
slots: ComponentSlot[];
cssProperties: ComponentCSSProperty[];
cssParts: ComponentCSSPart[];
deprecated?: boolean | string;
className?: string;
jsDoc?: JsDoc;
Expand Down
31 changes: 28 additions & 3 deletions src/cli/transformer/json/json-transformer.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
import { SimpleType, toTypeString } from "ts-simple-type";
import { Program, Type, TypeChecker } from "typescript";
import { ComponentCSSProperty } from "../../../analyze";
import { AnalyzeComponentsResult } from "../../../analyze/analyze-components";
import { ComponentCSSPart } from "../../../analyze/types/component-css-part";
import { ComponentDefinition } from "../../../analyze/types/component-definition";
import { ComponentMember } from "../../../analyze/types/component-member";
import { ComponentSlot } from "../../../analyze/types/component-slot";
import { EventDeclaration } from "../../../analyze/types/event-types";
import { JsDoc } from "../../../analyze/types/js-doc";
import { flatten } from "../../util";
import { WcaCliConfig } from "../../wca-cli-arguments";
import { HtmlData, HtmlDataAttribute, HtmlDataEvent, HtmlDataProperty, HtmlDataSlot, HtmlDataTag, HtmlDataCssProperty } from "./vscode-html-data";
import { ComponentCSSProperty } from "../../../analyze";
import {
HtmlData,
HtmlDataAttribute,
HtmlDataCssPart,
HtmlDataCssProperty,
HtmlDataEvent,
HtmlDataProperty,
HtmlDataSlot,
HtmlDataTag
} from "./vscode-html-data";

/**
* Transforms results to json.
Expand Down Expand Up @@ -50,9 +60,15 @@ function definitionToHtmlDataTag(definition: ComponentDefinition, checker: TypeC
const slots = definition.declaration.slots
.map(e => componentSlotToHtmlDataSlot(e, checker))
.filter((val): val is NonNullable<typeof val> => val != null);

const cssProperties = definition.declaration.cssProperties
.map(p => componentCssPropToHtmlCssProp(p, checker))
.filter((val): val is NonNullable<typeof val> => val != null);

const cssParts = definition.declaration.cssParts
.map(p => componentCssPropToHtmlCssPart(p, checker))
.filter((val): val is NonNullable<typeof val> => val != null);

return {
name: definition.tagName,
description: getDescriptionFromJsDoc(definition.declaration.jsDoc),
Expand All @@ -61,7 +77,8 @@ function definitionToHtmlDataTag(definition: ComponentDefinition, checker: TypeC
properties,
events,
slots,
cssProperties
cssProperties,
cssParts
};
}

Expand All @@ -73,6 +90,14 @@ function componentCssPropToHtmlCssProp(prop: ComponentCSSProperty, checker: Type
};
}

function componentCssPropToHtmlCssPart(part: ComponentCSSPart, checker: TypeChecker): HtmlDataCssPart | undefined {
return {
name: part.name || "",
description: getDescriptionFromJsDoc(part.jsDoc),
jsDoc: getJsDocTextFromJsDoc(part.jsDoc)
};
}

function componentSlotToHtmlDataSlot(slot: ComponentSlot, checker: TypeChecker): HtmlDataSlot | undefined {
return {
name: slot.name || "",
Expand Down
3 changes: 3 additions & 0 deletions src/cli/transformer/json/vscode-html-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ export interface HtmlDataEvent extends HtmlDataMember {}

export interface HtmlDataCssProperty extends HtmlDataMember {}

export interface HtmlDataCssPart extends HtmlDataMember {}

export interface HtmlDataTag {
name: string;
description?: string;
Expand All @@ -40,6 +42,7 @@ export interface HtmlDataTag {
slots?: HtmlDataSlot[];
events?: HtmlDataEvent[];
cssProperties?: HtmlDataCssProperty[];
cssParts?: HtmlDataCssPart[];
}

export interface HtmlDataV2 {
Expand Down
25 changes: 21 additions & 4 deletions src/cli/transformer/markdown/markdown-transformer.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { isAssignableToSimpleTypeKind, SimpleTypeKind, toTypeString } from "ts-simple-type";
import { Program, TypeChecker } from "typescript";
import { AnalyzeComponentsResult } from "../../../analyze/analyze-components";
import { ComponentCSSPart } from "../../../analyze/types/component-css-part";
import { ComponentCSSProperty } from "../../../analyze/types/component-css-property";
import { ComponentMemberAttribute, ComponentMemberProperty } from "../../../analyze/types/component-member";
import { ComponentSlot } from "../../../analyze/types/component-slot";
Expand Down Expand Up @@ -39,6 +40,7 @@ export function markdownTransformer(results: AnalyzeComponentsResult[], program:
const slots = declaration.slots.sort((a, b) => (a.name == null ? -1 : b.name == null ? 1 : a.name < b.name ? -1 : 1));
const events = declaration.events.sort((a, b) => (a.name < b.name ? -1 : 1));
const cssProps = declaration.cssProperties.sort((a, b) => (a.name < b.name ? -1 : 1));
const cssParts = declaration.cssParts.sort((a, b) => (a.name < b.name ? -1 : 1));

if (attributes.length > 0) {
segmentText += "\n" + memberAttributeSection(attributes, program.getTypeChecker(), config);
Expand All @@ -52,14 +54,18 @@ export function markdownTransformer(results: AnalyzeComponentsResult[], program:
segmentText += "\n" + eventSection(events, config, program.getTypeChecker());
}

if (cssProps.length > 0) {
segmentText += "\n" + cssPropSection(cssProps, config);
}

if (slots.length > 0) {
segmentText += "\n" + slotSection(slots, config);
}

if (cssParts.length > 0) {
segmentText += "\n" + cssPartSection(cssParts, config);
}

if (cssProps.length > 0) {
segmentText += "\n" + cssPropSection(cssProps, config);
}

return segmentText;
});

Expand All @@ -77,6 +83,17 @@ function cssPropSection(cssProperty: ComponentCSSProperty[], config: WcaCliConfi
return markdownHeader("CSS Custom Properties", 2, config) + "\n" + markdownTable(rows);
}

/**
* Returns a markdown table with css parts
* @param cssPart
* @param config
*/
function cssPartSection(cssPart: ComponentCSSPart[], config: WcaCliConfig): string {
const rows: string[][] = [["Part", "Description"]];
rows.push(...cssPart.map(part => [(part.name && markdownHighlight(part.name)) || "", (part.jsDoc && part.jsDoc.comment) || ""]));
return markdownHeader("CSS Shadow Parts", 2, config) + "\n" + markdownTable(rows);
}

/**
* Returns a markdown table with events
* @param events
Expand Down
Loading

0 comments on commit 366d651

Please sign in to comment.