Skip to content

Commit

Permalink
Add support event types in JSDoc. Fixes #165
Browse files Browse the repository at this point in the history
  • Loading branch information
runem committed Jul 12, 2020
1 parent 432d31f commit d802a91
Show file tree
Hide file tree
Showing 21 changed files with 300 additions and 95 deletions.
3 changes: 2 additions & 1 deletion dev/src/custom-element/custom-element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ export class CustomElement extends MySuperClass {
set attr1(val: string) {}

onClick() {
new CustomEvent("my-custom-event");
this.dispatchEvent(new CustomEvent("my-custom-event", { detail: "hello" }));
this.dispatchEvent(new MouseEvent("mouse-move"));
}
}

Expand Down
2 changes: 1 addition & 1 deletion dev/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"experimentalDecorators": true,
"target": "es5",
"module": "commonjs",
"lib": ["esnext", "dom"],
"lib": ["ESnext", "DOM"],
"strict": true,
"esModuleInterop": true
}
Expand Down
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
"homepage": "https://github.com/runem/web-component-analyzer#readme",
"dependencies": {
"fast-glob": "^3.2.2",
"ts-simple-type": "~1.0.0",
"ts-simple-type": "~1.0.4",
"typescript": "^3.8.3",
"yargs": "^15.3.1"
},
Expand Down Expand Up @@ -87,7 +87,7 @@
"test/**/*.ts",
"!test/{helpers,snapshots}/**/*"
],
"timeout": "200s"
"timeout": "2m"
},
"husky": {
"hooks": {
Expand Down
20 changes: 19 additions & 1 deletion src/analyze/analyze-text.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { existsSync, readFileSync } from "fs";
import { dirname, join } from "path";
import * as tsModule from "typescript";
import { CompilerOptions, Program, ScriptKind, ScriptTarget, SourceFile, System, TypeChecker } from "typescript";
//import * as ts from "typescript";
Expand All @@ -10,6 +12,7 @@ export interface IVirtualSourceFile {
fileName: string;
text?: string;
analyze?: boolean;
includeLib?: boolean;
}

export type VirtualSourceFile = IVirtualSourceFile | string;
Expand Down Expand Up @@ -45,9 +48,24 @@ export function analyzeText(inputFiles: VirtualSourceFile[] | VirtualSourceFile,
)
.map(file => ({ ...file, fileName: file.fileName }));

const includeLib = files.some(file => file.includeLib);

const readFile = (fileName: string): string | undefined => {
const matchedFile = files.find(currentFile => currentFile.fileName === fileName);
return matchedFile == null ? undefined : matchedFile.text;
if (matchedFile != null) {
return matchedFile.text;
}

if (includeLib) {
// TODO: find better method of finding the current typescript module path
fileName = fileName.match(/[/\\]/) ? fileName : join(dirname(require.resolve("typescript")), fileName);
}

if (existsSync(fileName)) {
return readFileSync(fileName, "utf8").toString();
}

return undefined;
};

const fileExists = (fileName: string): boolean => {
Expand Down
3 changes: 2 additions & 1 deletion src/analyze/analyzer-visit-context.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as tsModule from "typescript";
import { Node, SourceFile, TypeChecker } from "typescript";
import { Node, SourceFile, TypeChecker, Program } from "typescript";
import { AnalyzerFlavor, ComponentFeatureCollection } from "./flavors/analyzer-flavor";
import { AnalyzerConfig } from "./types/analyzer-config";
import { ComponentDeclaration } from "./types/component-declaration";
Expand All @@ -10,6 +10,7 @@ import { ComponentDeclaration } from "./types/component-declaration";
*/
export interface AnalyzerVisitContext {
checker: TypeChecker;
program: Program;
ts: typeof tsModule;
config: AnalyzerConfig;
flavors: AnalyzerFlavor[];
Expand Down
40 changes: 27 additions & 13 deletions src/analyze/flavors/custom-element/discover-events.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,30 @@
import { SimpleType } from "ts-simple-type";
import { Node } from "typescript";
import { AnalyzerVisitContext } from "../../analyzer-visit-context";
import { ComponentEvent } from "../../types/features/component-event";
import { getJsDoc } from "../../util/js-doc-util";
import { lazy } from "../../util/lazy";
import { resolveNodeValue } from "../../util/resolve-node-value";

const EVENT_NAMES = [
"Event",
"CustomEvent",
"AnimationEvent",
"ClipboardEvent",
"DragEvent",
"FocusEvent",
"HashChangeEvent",
"InputEvent",
"KeyboardEvent",
"MouseEvent",
"PageTransitionEvent",
"PopStateEvent",
"ProgressEvent",
"StorageEvent",
"TouchEvent",
"TransitionEvent",
"UiEvent",
"WheelEvent"
];

/**
* Discovers events dispatched
Expand All @@ -15,14 +36,14 @@ export function discoverEvents(node: Node, context: AnalyzerVisitContext): Compo

// new CustomEvent("my-event");
if (ts.isNewExpression(node)) {
const { expression, arguments: args, typeArguments } = node;
const { expression, arguments: args } = node;

if (expression.getText() === "CustomEvent" && args && args.length >= 1) {
if (EVENT_NAMES.includes(expression.getText()) && args && args.length >= 1) {
const arg = args[0];

if (ts.isStringLiteralLike(arg)) {
const eventName = arg.text;
const eventName = resolveNodeValue(arg, context)?.value;

if (typeof eventName === "string") {
// Either grab jsdoc from the new expression or from a possible call expression that its wrapped in
const jsDoc =
getJsDoc(expression, ts) ||
Expand All @@ -35,14 +56,7 @@ export function discoverEvents(node: Node, context: AnalyzerVisitContext): Compo
jsDoc,
name: eventName,
node,
type: lazy(() => {
return (
(typeArguments?.[0] != null && checker.getTypeFromTypeNode(typeArguments[0])) ||
({
kind: "ANY"
} as SimpleType)
);
})
type: lazy(() => checker.getTypeAtLocation(node))
}
];
}
Expand Down
8 changes: 4 additions & 4 deletions src/analyze/flavors/js-doc/discover-features.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export const discoverFeatures: Partial<FeatureDiscoverVisitMap<AnalyzerVisitCont
return {
name: name,
jsDoc: description != null ? { description } : undefined,
type: lazy(() => (type && parseSimpleJsDocTypeExpression(type)) || { kind: "ANY" }),
type: type != null ? lazy(() => parseSimpleJsDocTypeExpression(type, context) || { kind: "ANY" }) : undefined,
typeHint: type,
node: tagNode
};
Expand All @@ -77,7 +77,7 @@ export const discoverFeatures: Partial<FeatureDiscoverVisitMap<AnalyzerVisitCont
(tagNode, { name, type, description }) => {
// Grab the type from jsdoc and use it to find permitted tag names
// Example: @slot {"div"|"span"} myslot
const permittedTagNameType = type == null ? undefined : parseSimpleJsDocTypeExpression(type);
const permittedTagNameType = type == null ? undefined : parseSimpleJsDocTypeExpression(type, context);
const permittedTagNames: string[] | undefined = (() => {
if (permittedTagNameType == null) {
return undefined;
Expand Down Expand Up @@ -120,7 +120,7 @@ export const discoverFeatures: Partial<FeatureDiscoverVisitMap<AnalyzerVisitCont
propName: name,
jsDoc: description != null ? { description } : undefined,
typeHint: type,
type: lazy(() => (type && parseSimpleJsDocTypeExpression(type)) || { kind: "ANY" }),
type: lazy(() => (type && parseSimpleJsDocTypeExpression(type, context)) || { kind: "ANY" }),
node: tagNode,
default: def,
visibility: undefined,
Expand All @@ -143,7 +143,7 @@ export const discoverFeatures: Partial<FeatureDiscoverVisitMap<AnalyzerVisitCont
kind: "attribute",
attrName: name,
jsDoc: description != null ? { description } : undefined,
type: lazy(() => (type && parseSimpleJsDocTypeExpression(type)) || { kind: "ANY" }),
type: lazy(() => (type && parseSimpleJsDocTypeExpression(type, context)) || { kind: "ANY" }),
typeHint: type,
node: tagNode,
default: def,
Expand Down
29 changes: 17 additions & 12 deletions src/analyze/flavors/js-doc/refine-feature.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { ComponentEvent } from "../../types/features/component-event";
import { AnalyzerVisitContext } from "../../analyzer-visit-context";
import { ComponentMember, ComponentMemberReflectKind } from "../../types/features/component-member";
import { ComponentMethod } from "../../types/features/component-method";
import { JsDoc } from "../../types/js-doc";
import { VisibilityKind } from "../../types/visibility-kind";
import { parseSimpleJsDocTypeExpression } from "../../util/js-doc-util";
Expand All @@ -11,7 +10,7 @@ import { AnalyzerFlavor } from "../analyzer-flavor";
* Refines features by looking at the jsdoc tags on the feature
*/
export const refineFeature: AnalyzerFlavor["refineFeature"] = {
event: (event: ComponentEvent) => {
event: (event, context) => {
if (event.jsDoc == null || event.jsDoc.tags == null) return event;

// Check if the feature has "@ignore" jsdoc tag
Expand All @@ -20,23 +19,26 @@ export const refineFeature: AnalyzerFlavor["refineFeature"] = {
}

return [applyJsDocDeprecated, applyJsDocVisibility, applyJsDocType].reduce(
(event, applyFunc) => (applyFunc as Function)(event, event.jsDoc),
(event, applyFunc) => (applyFunc as Function)(event, event.jsDoc, context),
event
);
},
method: (method: ComponentMethod) => {
method: (method, context) => {
if (method.jsDoc == null || method.jsDoc.tags == null) return method;

// Check if the feature has "@ignore" jsdoc tag
if (hasIgnoreJsDocTag(method.jsDoc)) {
return undefined;
}

method = [applyJsDocDeprecated, applyJsDocVisibility].reduce((method, applyFunc) => (applyFunc as Function)(method, method.jsDoc), method);
method = [applyJsDocDeprecated, applyJsDocVisibility].reduce(
(method, applyFunc) => (applyFunc as Function)(method, method.jsDoc, context),
method
);

return method;
},
member: (member: ComponentMember) => {
member: (member, context) => {
// Return right away if the member doesn't have jsdoc
if (member.jsDoc == null || member.jsDoc.tags == null) return member;

Expand All @@ -54,7 +56,7 @@ export const refineFeature: AnalyzerFlavor["refineFeature"] = {
applyJsDocType,
applyJsDocAttribute,
applyJsDocModifiers
].reduce((member, applyFunc) => (applyFunc as Function)(member, member.jsDoc), member);
].reduce((member, applyFunc) => (applyFunc as Function)(member, member.jsDoc, context), member);
}
};

Expand Down Expand Up @@ -122,10 +124,12 @@ function applyJsDocVisibility<T extends Partial<Pick<ComponentMember, "visibilit
* Applies the "@attribute" jsdoc tag
* @param feature
* @param jsDoc
* @param context
*/
function applyJsDocAttribute<T extends Partial<Pick<ComponentMember, "propName" | "attrName" | "default" | "type" | "typeHint">>>(
feature: T,
jsDoc: JsDoc
jsDoc: JsDoc,
context: AnalyzerVisitContext
): T {
const attributeTag = jsDoc.tags?.find(tag => ["attr", "attribute"].includes(tag.tag));

Expand All @@ -141,7 +145,7 @@ function applyJsDocAttribute<T extends Partial<Pick<ComponentMember, "propName"
// @attr jsdoc tag can also include the type of attribute
if (parsed.type != null && result.typeHint == null) {
result.typeHint = parsed.type;
result.type = feature.type ?? lazy(() => parseSimpleJsDocTypeExpression(parsed.type || ""));
result.type = feature.type ?? lazy(() => parseSimpleJsDocTypeExpression(parsed.type || "", context));
}

return result;
Expand Down Expand Up @@ -237,8 +241,9 @@ function applyJsDocReflect<T extends Partial<Pick<ComponentMember, "reflect">>>(
* Applies the "@type" jsdoc tag
* @param feature
* @param jsDoc
* @param context
*/
function applyJsDocType<T extends Partial<Pick<ComponentMember, "type" | "typeHint">>>(feature: T, jsDoc: JsDoc): T {
function applyJsDocType<T extends Partial<Pick<ComponentMember, "type" | "typeHint">>>(feature: T, jsDoc: JsDoc, context: AnalyzerVisitContext): T {
const typeTag = jsDoc.tags?.find(tag => tag.tag === "type");

if (typeTag != null && feature.typeHint == null) {
Expand All @@ -248,7 +253,7 @@ function applyJsDocType<T extends Partial<Pick<ComponentMember, "type" | "typeHi
return {
...feature,
typeHint: parsed.type,
type: feature.type ?? lazy(() => parseSimpleJsDocTypeExpression(parsed.type || ""))
type: feature.type ?? lazy(() => parseSimpleJsDocTypeExpression(parsed.type || "", context))
};
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/analyze/flavors/lit-element/discover-members.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ function parseStaticProperties(returnStatement: ReturnStatement, context: Analyz
priority: "high",
kind: "property",
type: lazy(() => {
return (jsDoc && getJsDocType(jsDoc)) || (typeof litConfig.type === "object" && litConfig.type) || { kind: "ANY" };
return (jsDoc && getJsDocType(jsDoc, context)) || (typeof litConfig.type === "object" && litConfig.type) || { kind: "ANY" };
}),
propName: propName,
attrName: emitAttribute ? attrName : undefined,
Expand Down
1 change: 1 addition & 0 deletions src/analyze/make-context-from-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export function makeContextFromConfig(options: AnalyzerOptions): AnalyzerVisitCo
// Create context
return {
checker,
program: options.program,
ts,
flavors,
cache: {
Expand Down
4 changes: 3 additions & 1 deletion src/analyze/stages/merge/merge-feature.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,9 @@ export function mergeEvents(events: ComponentEvent[]): ComponentEvent[] {
event => event.name,
(left, right) => ({
...left,
jsDoc: mergeJsDoc(left.jsDoc, right.jsDoc)
jsDoc: mergeJsDoc(left.jsDoc, right.jsDoc),
type: () => (left.type != null ? left.type() : right.type != null ? right.type() : { kind: "ANY" }),
typeHint: left.typeHint || right.typeHint
})
);
}
2 changes: 1 addition & 1 deletion src/analyze/types/features/component-event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { ComponentFeatureBase } from "./component-feature";
export interface ComponentEvent extends ComponentFeatureBase {
name: string;
node: Node;
type: () => SimpleType | Type;
type?: () => SimpleType | Type;
typeHint?: string;
visibility?: VisibilityKind;
deprecated?: boolean | string;
Expand Down
Loading

0 comments on commit d802a91

Please sign in to comment.