-
Notifications
You must be signed in to change notification settings - Fork 879
/
lit-element.ts
134 lines (125 loc) · 3.95 KB
/
lit-element.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
/**
* @license
* Copyright 2022 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/
/**
* @fileoverview
*
* Utilities for analyzing LitElement (and ReactiveElement) declarations.
*/
import ts from 'typescript';
import {getClassMembers, getHeritage} from '../javascript/classes.js';
import {LitElementDeclaration, AnalyzerInterface} from '../model.js';
import {isCustomElementDecorator} from './decorators.js';
import {getProperties} from './properties.js';
import {
getJSDocData,
getTagName as getCustomElementTagName,
} from '../custom-elements/custom-elements.js';
/**
* Gets an analyzer LitElementDeclaration object from a ts.ClassDeclaration
* (branded as LitClassDeclaration).
*/
export const getLitElementDeclaration = (
declaration: LitClassDeclaration,
analyzer: AnalyzerInterface
): LitElementDeclaration => {
return new LitElementDeclaration({
tagname: getTagName(declaration),
// TODO(kschaaf): support anonymous class expressions when assigned to a const
name: declaration.name?.text ?? '',
node: declaration,
reactiveProperties: getProperties(declaration, analyzer),
...getJSDocData(declaration, analyzer),
getHeritage: () => getHeritage(declaration, analyzer),
...getClassMembers(declaration, analyzer),
});
};
/**
* Returns true if this type represents the actual LitElement class.
*/
const _isLitElementClassDeclaration = (
t: ts.BaseType,
analyzer: AnalyzerInterface
) => {
// TODO: should we memoize this for performance?
const declarations = t.getSymbol()?.getDeclarations();
if (declarations?.length !== 1) {
return false;
}
const node = declarations[0];
return _isLitElement(node) || isLitElementSubclass(node, analyzer);
};
/**
* Returns true if the given declaration is THE LitElement declaration.
*
* TODO(kschaaf): consider a less brittle method of detecting canonical
* LitElement
*/
const _isLitElement = (node: ts.Declaration) => {
return (
_isLitElementModule(node.getSourceFile()) &&
ts.isClassDeclaration(node) &&
node.name?.text === 'LitElement'
);
};
/**
* Returns true if the given source file is THE lit-element source file.
*/
const _isLitElementModule = (file: ts.SourceFile) => {
return (
file.fileName.endsWith('/node_modules/lit-element/lit-element.d.ts') ||
// Handle case of running analyzer in symlinked monorepo
file.fileName.endsWith('/packages/lit-element/lit-element.d.ts')
);
};
/**
* This type identifies a ClassDeclaration as one that inherits from LitElement.
*
* It lets isLitElement function as a type predicate that returns whether or
* not its argument is a LitElement such that when it returns false TypeScript
* doesn't infer that the argument is not a ClassDeclaration.
*/
export type LitClassDeclaration = ts.ClassDeclaration & {
__litBrand: never;
};
/**
* Returns true if `node` is a ClassLikeDeclaration that extends LitElement.
*/
export const isLitElementSubclass = (
node: ts.Node,
analyzer: AnalyzerInterface
): node is LitClassDeclaration => {
if (!ts.isClassLike(node)) {
return false;
}
const checker = analyzer.program.getTypeChecker();
const type = checker.getTypeAtLocation(node) as ts.InterfaceType;
const baseTypes = checker.getBaseTypes(type);
for (const t of baseTypes) {
if (_isLitElementClassDeclaration(t, analyzer)) {
return true;
}
}
return false;
};
/**
* Returns the tagname associated with a LitClassDeclaration
* @param declaration
* @returns
*/
export const getTagName = (declaration: LitClassDeclaration) => {
const customElementDecorator = declaration.decorators?.find(
isCustomElementDecorator
);
if (
customElementDecorator !== undefined &&
customElementDecorator.expression.arguments.length === 1 &&
ts.isStringLiteral(customElementDecorator.expression.arguments[0])
) {
// Get tag from decorator: `@customElement('x-foo')`
return customElementDecorator.expression.arguments[0].text;
}
return getCustomElementTagName(declaration);
};