Skip to content

Commit

Permalink
Read jsdoc @Private and @Protected annotations
Browse files Browse the repository at this point in the history
  • Loading branch information
runem committed Oct 6, 2019
1 parent 8ec3f27 commit a6965fa
Show file tree
Hide file tree
Showing 33 changed files with 1,636 additions and 341 deletions.
7 changes: 7 additions & 0 deletions dev/src/lit-element/lit-element.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,15 @@ import { customElement, LitElement } from "lit-element";
export class MyElement extends LitElement {
@property() myBoolean = true;

/**
* @private
*/
myString = "hello";

/**
* @protected
* @type {string}
*/
myProp = "hejsa";

static get properties() {
Expand Down
2 changes: 2 additions & 0 deletions src/analyze/analyze-components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { parseComponentDefinitions } from "./parse/parse-definitions";
import { parseGlobalEvents } from "./parse/parse-global-events";
import { ComponentDefinition } from "./types/component-definition";
import { ComponentDiagnostic } from "./types/component-diagnostic";
import { ComponentMemberVisibilityKind } from "./types/component-member";
import { EventDeclaration } from "./types/event-types";

const DEFAULT_FLAVORS = [new LitElementFlavor(), new CustomElementFlavor(), new JsDocFlavor(), new StencilFlavor()];
Expand All @@ -28,6 +29,7 @@ export interface AnalyzeComponentsOptions {
*/
export interface AnalyzeComponentsConfig {
diagnostics?: boolean;
visibility?: ComponentMemberVisibilityKind;
analyzeLibDom?: boolean;
excludedDeclarationNames?: string[];
}
Expand Down
7 changes: 7 additions & 0 deletions src/analyze/flavors/custom-element/custom-element-flavor.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Node } from "typescript";
import { ComponentMember } from "../../types/component-member";
import { EventDeclaration } from "../../types/event-types";
import { isNodeInLibDom } from "../../util/ast-util";
import {
ParseComponentFlavor,
ParseComponentMembersContext,
Expand Down Expand Up @@ -32,4 +33,10 @@ export class CustomElementFlavor implements ParseComponentFlavor {
visitGlobalEvents(node: Node, context: ParseVisitContextGlobalEvents): void {
visitGlobalEvents(node, context);
}

isNodeInLib(node: Node) {
if (isNodeInLibDom(node)) {
return true;
}
}
}
23 changes: 16 additions & 7 deletions src/analyze/flavors/custom-element/parse-declaration-members.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { SimpleTypeKind, toSimpleType } from "ts-simple-type";
import { BinaryExpression, ExpressionStatement, Node, ReturnStatement } from "typescript";
import { ComponentMember } from "../../types/component-member";
import { hasModifier, hasPublicSetter, isPropertyRequired, isPropNamePublic } from "../../util/ast-util";
import { hasModifier, isPropertyRequired, isNamePrivate, getMemberVisibility, isMemberAndWritable } from "../../util/ast-util";
import { getJsDoc } from "../../util/js-doc-util";
import { resolveNodeValue } from "../../util/resolve-node-value";
import { relaxType } from "../../util/type-util";
Expand All @@ -26,6 +26,7 @@ export function parseDeclarationMembers(node: Node, context: ParseComponentMembe
members.push({
kind: "attribute",
attrName,
visibility: isNamePrivate(attrName) ? "private" : "public",
type: { kind: SimpleTypeKind.ANY },
node: attrNameNode
});
Expand All @@ -38,17 +39,19 @@ export function parseDeclarationMembers(node: Node, context: ParseComponentMembe
}

// class { myProp = "hello"; }
else if ((ts.isPropertyDeclaration(node) || ts.isPropertySignature(node)) && hasPublicSetter(node, ts)) {
else if ((ts.isPropertyDeclaration(node) || ts.isPropertySignature(node)) && isMemberAndWritable(node, ts)) {
const { name, initializer } = node;

if (ts.isIdentifier(name) || ts.isStringLiteralLike(name)) {
// Find default value based on initializer
const def = "initializer" in node && node.initializer != null ? resolveNodeValue(initializer, context) : undefined;
const propName = name.text;

return [
{
kind: "property",
propName: name.text,
propName,
visibility: getMemberVisibility(node, ts),
type: checker.getTypeAtLocation(node),
required: isPropertyRequired(node, context.checker),
default: def,
Expand All @@ -61,10 +64,13 @@ export function parseDeclarationMembers(node: Node, context: ParseComponentMembe

// class { 'hello'?: number }
else if (ts.isConditionalExpression(node) && (ts.isStringLiteralLike(node.condition) || ts.isIdentifier(node.condition))) {
const propName = node.condition.text;

return [
{
kind: "property",
propName: node.condition.text,
propName,
visibility: isNamePrivate(propName) ? "private" : "public",
type: checker.getTypeAtLocation(node),
jsDoc: getJsDoc(node, ts),
node
Expand All @@ -73,16 +79,18 @@ export function parseDeclarationMembers(node: Node, context: ParseComponentMembe
}

// class { set myProp(value: string) { ... } }
else if (ts.isSetAccessor(node) && hasPublicSetter(node, ts)) {
else if (ts.isSetAccessor(node) && isMemberAndWritable(node, ts)) {
const { name, parameters } = node;

if (ts.isIdentifier(name) && parameters.length > 0) {
const parameter = parameters[0];
const propName = name.text;

return [
{
kind: "property",
propName: name.text,
propName,
visibility: getMemberVisibility(node, ts),
type: context.checker.getTypeAtLocation(parameter),
jsDoc: getJsDoc(node, ts),
required: false,
Expand Down Expand Up @@ -114,13 +122,14 @@ export function parseDeclarationMembers(node: Node, context: ParseComponentMembe

const parsedClassField = classFieldDeclaration == null ? undefined : parseDeclarationMembers(classFieldDeclaration, context);

if (isPropNamePublic(propName) && (classFieldDeclaration == null || parsedClassField != null)) {
if (classFieldDeclaration == null || parsedClassField != null) {
const simpleType = relaxType(toSimpleType(checker.getTypeAtLocation(right), checker));

members.push({
kind: "property",
propName,
default: resolveNodeValue(right, context),
visibility: isNamePrivate(propName) ? "private" : "public",
type: simpleType,
jsDoc: getJsDoc(assignment.parent, ts),
required: false,
Expand Down
14 changes: 10 additions & 4 deletions src/analyze/flavors/js-doc/parse-declaration-members.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { SimpleTypeKind } from "ts-simple-type";
import { Node } from "typescript";
import { ComponentMember, ComponentMemberAttribute, ComponentMemberProperty } from "../../types/component-member";
import { isNamePrivate } from "../../util/ast-util";
import { parseJsDocTypeString } from "../../util/js-doc-util";
import { ParseComponentMembersContext } from "../parse-component-flavor";
import { parseJsDocForNode } from "./helper";
Expand All @@ -18,10 +19,13 @@ export function parseDeclarationMembers(node: Node, context: ParseComponentMembe
node,
["prop", "property"],
(tagNode, parsed) => {
if (parsed.name != null) {
const propName = parsed.name;

if (propName != null) {
return {
kind: "property",
propName: parsed.name,
propName,
visibility: isNamePrivate(propName) ? "private" : "public",
jsDoc: parsed.comment != null ? { comment: parsed.comment } : undefined,
type: (parsed.type && parseJsDocTypeString(parsed.type)) || { kind: SimpleTypeKind.ANY },
node: tagNode
Expand All @@ -35,10 +39,12 @@ export function parseDeclarationMembers(node: Node, context: ParseComponentMembe
node,
["attr", "attribute"],
(tagNode, parsed) => {
if (parsed.name != null) {
const attrName = parsed.name;
if (attrName != null) {
return {
kind: "attribute",
attrName: parsed.name,
attrName,
visibility: isNamePrivate(attrName) ? "private" : "public",
jsDoc: parsed.comment && { comment: parsed.comment },
type: (parsed.type && parseJsDocTypeString(parsed.type)) || { kind: SimpleTypeKind.ANY },
node: tagNode
Expand Down
9 changes: 9 additions & 0 deletions src/analyze/flavors/lit-element/lit-element-flavor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,13 @@ export class LitElementFlavor implements ParseComponentFlavor {
parseDeclarationMembers(node: Node, context: ParseComponentMembersContext): ComponentMember[] | undefined {
return parseDeclarationMembers(node, context);
}

isNodeInLib(node: Node, context: ParseComponentMembersContext) {
if (context.ts.isClassLike(node)) {
const name = (node.name != null && node.name.text) || "";
if (["LitElement", "PolymerElement", "Polymer.Element"].includes(name)) {
return true;
}
}
}
}
8 changes: 5 additions & 3 deletions src/analyze/flavors/lit-element/parse-declaration-members.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { isAssignableToSimpleTypeKind, SimpleType, SimpleTypeKind, toSimpleType, toTypeString } from "ts-simple-type";
import { Node, PropertyLikeDeclaration, PropertySignature, ReturnStatement, SetAccessorDeclaration } from "typescript";
import { ComponentMember } from "../../types/component-member";
import { hasModifier, hasPublicSetter, isPropertyRequired, isPropNamePublic } from "../../util/ast-util";
import { getMemberVisibility, hasModifier, isMemberAndWritable, isNamePrivate, isPropertyRequired } from "../../util/ast-util";
import { isValidAttributeName } from "../../util/is-valid-attribute-name";
import { getJsDoc, getJsDocType } from "../../util/js-doc-util";
import { resolveNodeValue } from "../../util/resolve-node-value";
Expand Down Expand Up @@ -36,7 +36,7 @@ export function parseDeclarationMembers(node: Node, context: ParseComponentMembe
}

// @property({type: String}) myProp = "hello";
else if ((ts.isSetAccessorDeclaration(node) || ts.isPropertyDeclaration(node) || ts.isPropertySignature(node)) && hasPublicSetter(node, ts)) {
else if ((ts.isSetAccessorDeclaration(node) || ts.isPropertyDeclaration(node) || ts.isPropertySignature(node)) && isMemberAndWritable(node, ts)) {
return parsePropertyDecorator(node, context);
}
}
Expand Down Expand Up @@ -101,6 +101,7 @@ function parsePropertyDecorator(
attrName,
type,
node,
visibility: getMemberVisibility(node, ts),
default: def || litConfig.default,
required,
jsDoc
Expand Down Expand Up @@ -156,7 +157,7 @@ function parseStaticProperties(returnStatement: ReturnStatement, context: Flavor
for (const propNode of returnStatement.expression.properties) {
// Get propName
const propName = propNode.name != null && ts.isIdentifier(propNode.name) ? propNode.name.text : undefined;
if (propName == null || !isPropNamePublic(propName)) {
if (propName == null) {
continue;
}

Expand Down Expand Up @@ -203,6 +204,7 @@ function parseStaticProperties(returnStatement: ReturnStatement, context: Flavor
members.push({
kind: "property",
type,
visibility: isNamePrivate(propName) ? "private" : "public",
propName: propName,
attrName: emitAttribute ? attrName : undefined,
jsDoc,
Expand Down
4 changes: 3 additions & 1 deletion src/analyze/flavors/parse-component-flavor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ export interface FlavorVisitContext {
checker: TypeChecker;
ts: typeof tsModule;
config: AnalyzeComponentsConfig;
features?: FlavorVisitContextFeatures;
emitContinue?(): void;
emitDiagnostics(diagnostic: ComponentDiagnostic): void;
features?: FlavorVisitContextFeatures;
}

export interface FlavorVisitContextFeatures {
Expand Down Expand Up @@ -53,4 +53,6 @@ export interface ParseComponentFlavor {
parseDeclarationCSSProps?(node: Node, context: ParseComponentMembersContext): ComponentCSSProperty[] | undefined;

visitGlobalEvents?(node: Node, context: ParseVisitContextGlobalEvents): void;

isNodeInLib?(node: Node, context: ParseComponentMembersContext): boolean | undefined;
}
5 changes: 4 additions & 1 deletion src/analyze/flavors/stencil/parse-declaration-members.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Node } from "typescript";
import { ComponentMember } from "../../types/component-member";
import { isNamePrivate } from "../../util/ast-util";
import { ParseComponentMembersContext } from "../parse-component-flavor";

/**
Expand All @@ -20,10 +21,12 @@ export function parseDeclarationMembers(node: Node, context: ParseComponentMembe
if (ts.isPropertyDeclaration(node) || ts.isPropertySignature(node) || ts.isConditionalExpression(node)) {
const name = ts.isConditionalExpression(node) ? node.condition : node.name;
if (ts.isIdentifier(name) || ts.isStringLiteralLike(name)) {
const attrName = name.text;
return [
{
kind: "attribute",
attrName: name.text,
attrName,
visibility: isNamePrivate(attrName) ? "private" : "public",
type: checker.getTypeAtLocation(node),
node
}
Expand Down
6 changes: 6 additions & 0 deletions src/analyze/flavors/stencil/stencil-flavor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,10 @@ export class StencilFlavor implements ParseComponentFlavor {
visitComponentDefinitions(node: Node, context: VisitComponentDefinitionContext): void {
visitComponentDefinitions(node, context);
}

isNodeInLib(node: Node) {
if (node.getSourceFile().fileName.endsWith("stencil.core.d.ts")) {
return true;
}
}
}
6 changes: 6 additions & 0 deletions src/analyze/parse/expand-from-js-doc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ export function expandMemberFromJsDoc(member: ComponentMember): ComponentMember
newMember.deprecated = deprecatedTag.comment || true;
}

// Check "@private" and "@protected
const visibilityTag = member.jsDoc.tags.find(t => t.tag === "private" || t.tag === "protected");
if (visibilityTag != null) {
newMember.visibility = visibilityTag.tag === "private" ? "private" : "protected";
}

// Check "@prop {Number} myProp - My comment"
if (newMember.kind === "property" && newMember.attrName == null) {
const attrNameTag = member.jsDoc.tags.find(t => ["attr", "attribute"].includes(t.tag));
Expand Down
21 changes: 18 additions & 3 deletions src/analyze/parse/merge-declarations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ import { Type, TypeChecker } from "typescript";
import { FlavorVisitContext } from "../flavors/parse-component-flavor";
import { ComponentCSSProperty } from "../types/component-css-property";
import { ComponentDeclaration } from "../types/component-declaration";
import { ComponentMember, ComponentMemberAttribute, ComponentMemberProperty } from "../types/component-member";
import { ComponentMember, ComponentMemberAttribute, ComponentMemberProperty, ComponentMemberVisibilityKind } from "../types/component-member";
import { ComponentSlot } from "../types/component-slot";
import { EventDeclaration } from "../types/event-types";
import { JsDoc } from "../types/js-doc";
import { compareVisibility } from "../util/component-util";
import { mergeJsDocs } from "./merge-js-docs";

/**
Expand Down Expand Up @@ -196,7 +197,8 @@ function mergeAttrIntoProp(prop: ComponentMemberProperty, attr: ComponentMemberA
default: attr.default || prop.default,
required: attr.required || prop.required,
jsDoc: attr.jsDoc || prop.jsDoc,
attrName: attr.attrName
attrName: attr.attrName,
visibility: mergeVisibility(attr.visibility, prop.visibility)
};
}

Expand All @@ -211,7 +213,8 @@ function mergeMemberIntoMember<T extends ComponentMemberProperty | ComponentMemb
return {
...b,
attrName: a.attrName || b.attrName,
type: mergeTypes(a.type, b.type, checker)
type: mergeTypes(a.type, b.type, checker),
visibility: mergeVisibility(a.visibility, b.visibility)
};
}

Expand All @@ -238,3 +241,15 @@ function mergeTypes(typeA: SimpleType | Type, typeB: SimpleType | Type, checker:
// Else return "typeB"
return typeB;
}

/**
* Merges two visibilities. Picks the lowest visibility.
* @param visibilityA
* @param visibilityB
*/
export function mergeVisibility(
visibilityA: ComponentMemberVisibilityKind,
visibilityB: ComponentMemberVisibilityKind
): ComponentMemberVisibilityKind {
return compareVisibility(visibilityA, visibilityB) < 0 ? visibilityA : visibilityB;
}
Loading

0 comments on commit a6965fa

Please sign in to comment.