Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Read jsdoc @private and @protected annotations #107

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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