Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .changeset/fifty-crabs-perform.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@lit-labs/analyzer': minor
---

Adds TypeScript node reference to analyzer model objects
86 changes: 52 additions & 34 deletions packages/labs/analyzer/src/lib/javascript/classes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,14 @@ export const getClassMembers = (
const staticFieldMap = new Map<string, ClassField>();
const methodMap = new Map<string, ClassMethod>();
const staticMethodMap = new Map<string, ClassMethod>();
const accessors = new Map<string, {get?: ts.Node; set?: ts.Node}>();
declaration.members.forEach((node) => {
const accessors = new Map<
string,
{get?: ts.AccessorDeclaration; set?: ts.AccessorDeclaration}
>();
declaration.members.forEach((member) => {
// Ignore non-implementation signatures of overloaded methods by checking
// for `node.body`.
if (typescript.isConstructorDeclaration(node) && node.body) {
if (typescript.isConstructorDeclaration(member) && member.body) {
// TODO(bennypowers): We probably want to see if this matches what TypeScript considers a field initialization.
// Maybe instead of iterating through the constructor statements, we walk the body looking for any
// assignment expression so that we get ones inside of if statements, in parenthesized expressions, etc.
Expand All @@ -119,48 +122,45 @@ export const getClassMembers = (
//
// This is ok for now because these are rare ways to "declare" a field,
// especially in web components where you shouldn't have constructor parameters.
node.body.statements.forEach((node) => {
member.body.statements.forEach((statement) => {
if (
typescript.isExpressionStatement(node) &&
typescript.isBinaryExpression(node.expression) &&
node.expression.operatorToken.kind ===
typescript.SyntaxKind.EqualsToken &&
typescript.isPropertyAccessExpression(node.expression.left) &&
node.expression.left.expression.kind ===
typescript.SyntaxKind.ThisKeyword
typescript.isExpressionStatement(statement) &&
isConstructorFieldInitializer(statement.expression, typescript)
) {
const name = node.expression.left.name.getText();
const name = statement.expression.left.name.getText();
fieldMap.set(
name,
new ClassField({
name,
type: getTypeForNode(node.expression.right, analyzer),
privacy: getPrivacy(typescript, node),
readonly: getIsReadonlyForNode(node, analyzer),
type: getTypeForNode(statement.expression.right, analyzer),
privacy: getPrivacy(typescript, statement),
readonly: getIsReadonlyForNode(statement, analyzer),
node: statement.expression,
})
);
}
});
} else if (typescript.isMethodDeclaration(node) && node.body) {
const info = getMemberInfo(typescript, node);
const name = node.name.getText();
} else if (typescript.isMethodDeclaration(member) && member.body) {
const info = getMemberInfo(typescript, member);
const name = member.name.getText();
(info.static ? staticMethodMap : methodMap).set(
name,
new ClassMethod({
...info,
...getFunctionLikeInfo(node, name, analyzer),
...parseNodeJSDocInfo(node, analyzer),
...getFunctionLikeInfo(member, name, analyzer),
...parseNodeJSDocInfo(member, analyzer),
node: member,
})
);
} else if (typescript.isPropertyDeclaration(node)) {
} else if (typescript.isPropertyDeclaration(member)) {
if (
!typescript.isIdentifier(node.name) &&
!typescript.isPrivateIdentifier(node.name)
!typescript.isIdentifier(member.name) &&
!typescript.isPrivateIdentifier(member.name)
) {
analyzer.addDiagnostic(
createDiagnostic({
typescript,
node,
node: member,
message:
'@lit-labs/analyzer only supports analyzing class properties ' +
'named with plain identifiers. This property was ignored.',
Expand All @@ -171,23 +171,24 @@ export const getClassMembers = (
return;
}

const info = getMemberInfo(typescript, node);
const info = getMemberInfo(typescript, member);

(info.static ? staticFieldMap : fieldMap).set(
node.name.getText(),
member.name.getText(),
new ClassField({
...info,
default: node.initializer?.getText(),
type: getTypeForNode(node, analyzer),
...parseNodeJSDocInfo(node, analyzer),
readonly: getIsReadonlyForNode(node, analyzer),
default: member.initializer?.getText(),
type: getTypeForNode(member, analyzer),
...parseNodeJSDocInfo(member, analyzer),
readonly: getIsReadonlyForNode(member, analyzer),
node: member,
})
);
} else if (typescript.isAccessor(node)) {
const name = node.name.getText();
} else if (typescript.isAccessor(member)) {
const name = member.name.getText();
const _accessors = accessors.get(name) ?? {};
if (typescript.isGetAccessor(node)) _accessors.get = node;
else if (typescript.isSetAccessor(node)) _accessors.set = node;
if (typescript.isGetAccessor(member)) _accessors.get = member;
else if (typescript.isSetAccessor(member)) _accessors.set = member;
accessors.set(name, _accessors);
}
});
Expand All @@ -203,6 +204,7 @@ export const getClassMembers = (
// TODO(bennypowers): derive from getter?
// default: ???
// TODO(bennypowers): reflect, etc?
node: (set ?? get)!,
})
);
}
Expand Down Expand Up @@ -337,3 +339,19 @@ export const getSuperClass = (
);
return undefined;
};

export const isConstructorFieldInitializer = (
expression: ts.Expression,
typescript: typeof ts
): expression is ConstructorFieldInitializer => {
return (
typescript.isBinaryExpression(expression) &&
expression.operatorToken.kind === typescript.SyntaxKind.EqualsToken &&
typescript.isPropertyAccessExpression(expression.left) &&
expression.left.expression.kind === typescript.SyntaxKind.ThisKeyword
);
};

type ConstructorFieldInitializer = ts.AssignmentExpression<ts.EqualsToken> & {
left: ts.PropertyAccessExpression & {expression: ts.ThisExpression};
};
2 changes: 2 additions & 0 deletions packages/labs/analyzer/src/lib/javascript/functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ export const getFunctionLikeInfo = (
name: info.name,
parameters: info.parameters,
return: info.return,
node: overload,
});
});
}
Expand All @@ -128,6 +129,7 @@ export const getFunctionLikeInfo = (
parameters: node.parameters.map((p) => getParameter(p, analyzer)),
return: getReturn(node, analyzer),
overloads,
node,
};
};

Expand Down
39 changes: 33 additions & 6 deletions packages/labs/analyzer/src/lib/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -274,37 +274,48 @@ export class Module {

interface DeclarationInit extends DeprecatableDescribed {
name: string;
node: ts.Node;
}

export abstract class Declaration {
readonly name: string;
readonly description?: string | undefined;
readonly summary?: string | undefined;
readonly deprecated?: string | boolean | undefined;
readonly node: ts.Node;

constructor(init: DeclarationInit) {
this.name = init.name;
this.description = init.description;
this.summary = init.summary;
this.deprecated = init.deprecated;
this.node = init.node;
}

isVariableDeclaration(): this is VariableDeclaration {
return this instanceof VariableDeclaration;
}

isClassDeclaration(): this is ClassDeclaration {
return this instanceof ClassDeclaration;
}

isLitElementDeclaration(): this is LitElementDeclaration {
return this instanceof LitElementDeclaration;
}

isFunctionDeclaration(): this is FunctionDeclaration {
return this instanceof FunctionDeclaration;
}

isClassField(): this is ClassField {
return this instanceof ClassField;
}

isClassMethod(): this is ClassMethod {
return this instanceof ClassMethod;
}

isCustomElementDeclaration(): this is CustomElementDeclaration {
return this instanceof CustomElementDeclaration;
}
Expand All @@ -316,29 +327,32 @@ export interface VariableDeclarationInit extends DeclarationInit {
}

export class VariableDeclaration extends Declaration {
readonly node:
readonly type: Type | undefined;
declare readonly node:
| ts.VariableDeclaration
| ts.ExportAssignment
| ts.EnumDeclaration;
readonly type: Type | undefined;

constructor(init: VariableDeclarationInit) {
super(init);
this.node = init.node;
this.type = init.type;
}
}

export interface FunctionLikeInit extends DeprecatableDescribed {
export interface FunctionLikeInit extends DeclarationInit {
name: string;
parameters?: Parameter[] | undefined;
return?: Return | undefined;
overloads?: FunctionOverloadDeclaration[] | undefined;
node: ts.FunctionLikeDeclaration;
}

export class FunctionDeclaration extends Declaration {
parameters?: Parameter[] | undefined;
return?: Return | undefined;
overloads?: FunctionOverloadDeclaration[] | undefined;
declare readonly node: ts.FunctionLikeDeclaration;

constructor(init: FunctionLikeInit) {
super(init);
this.parameters = init.parameters;
Expand Down Expand Up @@ -369,28 +383,36 @@ export interface ClassMethodInit extends FunctionLikeInit {
privacy?: Privacy | undefined;
inheritedFrom?: Reference | undefined;
source?: SourceReference | undefined;
node: ts.MethodDeclaration;
}

export class ClassMethod extends FunctionDeclaration {
static?: boolean | undefined;
privacy?: Privacy | undefined;
inheritedFrom?: Reference | undefined;
source?: SourceReference | undefined;
override readonly node: ts.MethodDeclaration;

constructor(init: ClassMethodInit) {
super(init);
this.static = init.static;
this.privacy = init.privacy;
this.inheritedFrom = init.inheritedFrom;
this.source = init.source;
this.node = init.node;
}
}

export interface ClassFieldInit extends PropertyLike {
export interface ClassFieldInit extends DeclarationInit, PropertyLike {
static?: boolean | undefined;
privacy?: Privacy | undefined;
inheritedFrom?: Reference | undefined;
source?: SourceReference | undefined;
readonly?: boolean | undefined;
node:
| ts.PropertyDeclaration
| ts.AssignmentExpression<ts.EqualsToken>
| ts.AccessorDeclaration;
}

export class ClassField extends Declaration {
Expand All @@ -401,6 +423,11 @@ export class ClassField extends Declaration {
readonly?: boolean | undefined;
type?: Type | undefined;
default?: string | undefined;
declare node:
| ts.PropertyDeclaration
| ts.AssignmentExpression<ts.EqualsToken>
| ts.AccessorDeclaration;

constructor(init: ClassFieldInit) {
super(init);
this.static = init.static;
Expand Down Expand Up @@ -428,13 +455,13 @@ export interface ClassDeclarationInit extends DeclarationInit {
}

export class ClassDeclaration extends Declaration {
readonly node: ts.ClassLikeDeclaration;
private _getHeritage: () => ClassHeritage;
private _heritage: ClassHeritage | undefined = undefined;
readonly _fieldMap: Map<string, ClassField>;
readonly _staticFieldMap: Map<string, ClassField>;
readonly _methodMap: Map<string, ClassMethod>;
readonly _staticMethodMap: Map<string, ClassMethod>;
override readonly node: ts.ClassLikeDeclaration;

constructor(init: ClassDeclarationInit) {
super(init);
Expand Down