Skip to content

Commit 9e7654c

Browse files
43081jJames Garbutt
authored andcommitted
add property config resolution
We will now resolve values of variables among other things passed to property decorators and returned from property getters. For example: ```ts const someVariable = {type: String}; // elsewhere... @Property(someVariable) ```
1 parent 02caf9a commit 9e7654c

File tree

3 files changed

+108
-106
lines changed

3 files changed

+108
-106
lines changed

src/analyze/flavors/lit-element/discover-members.ts

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { lazy } from "../../util/lazy";
77
import { resolveNodeValue } from "../../util/resolve-node-value";
88
import { camelToDashCase, isNamePrivate } from "../../util/text-util";
99
import { AnalyzerDeclarationVisitContext } from "../analyzer-flavor";
10-
import { getLitElementPropertyDecoratorConfig, getLitPropertyOptions, parseLitPropertyOption } from "./parse-lit-property-configuration";
10+
import { getLitElementPropertyDecoratorConfig, getLitPropertyOptions, getLitPropertyType } from "./parse-lit-property-configuration";
1111

1212
/**
1313
* Parses lit-related declaration members.
@@ -174,20 +174,18 @@ function parseStaticProperties(returnStatement: ReturnStatement, context: Analyz
174174

175175
// Parse the lit property config for this property
176176
// Treat non-object-literal-expressions like the "type" (to support Polymer specific syntax)
177-
const litConfig = ts.isPropertyAssignment(propNode)
178-
? ts.isObjectLiteralExpression(propNode.initializer)
179-
? getLitPropertyOptions(propNode.initializer, context)
180-
: inPolymerFlavorContext(context)
181-
? parseLitPropertyOption(
182-
{
183-
kind: "type",
184-
initializer: propNode.initializer,
185-
config: {}
186-
},
187-
context
188-
)
189-
: {}
190-
: {};
177+
let litConfig: LitElementPropertyConfig = {};
178+
if (ts.isPropertyAssignment(propNode)) {
179+
if (inPolymerFlavorContext(context) && !ts.isObjectLiteralExpression(propNode.initializer)) {
180+
litConfig = { type: getLitPropertyType(ts, propNode.initializer) };
181+
} else {
182+
const resolved = resolveNodeValue(propNode.initializer, context);
183+
184+
if (resolved) {
185+
litConfig = getLitPropertyOptions(resolved.node, resolved.value, context, litConfig);
186+
}
187+
}
188+
}
191189

192190
// Get attrName based on the litConfig
193191
const attrName = getLitAttributeName(propName, litConfig, context);
Lines changed: 79 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
import { CallExpression, Expression, Node, ObjectLiteralExpression } from "typescript";
1+
import { SimpleType } from "ts-simple-type";
2+
import * as tsModule from "typescript";
3+
import { CallExpression, Node, PropertyAssignment } from "typescript";
24
import { AnalyzerVisitContext } from "../../analyzer-visit-context";
35
import { LitElementPropertyConfig } from "../../types/features/lit-element-property-config";
46
import { resolveNodeValue } from "../../util/resolve-node-value";
@@ -40,8 +42,6 @@ export function getLitElementPropertyDecorator(
4042
* @param context
4143
*/
4244
export function getLitElementPropertyDecoratorConfig(node: Node, context: AnalyzerVisitContext): undefined | LitElementPropertyConfig {
43-
const { ts } = context;
44-
4545
// Get reference to a possible "@property" decorator.
4646
const decorator = getLitElementPropertyDecorator(node, context);
4747

@@ -61,124 +61,112 @@ export function getLitElementPropertyDecoratorConfig(node: Node, context: Analyz
6161
break;
6262
}
6363

64-
// Get lit options from the object literal expression
65-
return configNode != null && ts.isObjectLiteralExpression(configNode) ? getLitPropertyOptions(configNode, context, config) : config;
64+
if (configNode == null) {
65+
return config;
66+
}
67+
68+
const resolved = resolveNodeValue(configNode, context);
69+
70+
return resolved != null ? getLitPropertyOptions(resolved.node, resolved.value, context, config) : config;
6671
}
6772

6873
return undefined;
6974
}
7075

76+
function hasProperty<T extends string>(obj: object, key: T): obj is { [K in T]: unknown } {
77+
return Object.prototype.hasOwnProperty.call(obj, key);
78+
}
79+
80+
export function getLitPropertyType(ts: typeof tsModule, node: Node): SimpleType | string {
81+
const value = ts.isIdentifier(node) ? node.text : undefined;
82+
83+
switch (value) {
84+
case "String":
85+
case "StringConstructor":
86+
return { kind: "STRING" };
87+
case "Number":
88+
case "NumberConstructor":
89+
return { kind: "NUMBER" };
90+
case "Boolean":
91+
case "BooleanConstructor":
92+
return { kind: "BOOLEAN" };
93+
case "Array":
94+
case "ArrayConstructor":
95+
return { kind: "ARRAY", type: { kind: "ANY" } };
96+
case "Object":
97+
case "ObjectConstructor":
98+
return { kind: "OBJECT", members: [] };
99+
default:
100+
// This is an unknown type, so set the name as a string
101+
return node.getText();
102+
}
103+
}
104+
71105
/**
72106
* Parses an object literal expression and returns a lit property configuration.
73107
* @param node
74108
* @param existingConfig
75109
* @param context
76110
*/
77111
export function getLitPropertyOptions(
78-
node: ObjectLiteralExpression,
112+
node: Node,
113+
object: unknown,
79114
context: AnalyzerVisitContext,
80115
existingConfig: LitElementPropertyConfig = {}
81116
): LitElementPropertyConfig {
82117
const { ts } = context;
118+
const result: LitElementPropertyConfig = { ...existingConfig };
119+
let attributeInitializer: Node | undefined;
120+
let typeInitializer: Node | undefined;
83121

84-
// Build up the property configuration by looking at properties in the object literal expression
85-
return node.properties.reduce((config, property) => {
86-
if (!ts.isPropertyAssignment(property)) return config;
87-
88-
const initializer = property.initializer;
89-
const kind = ts.isIdentifier(property.name) ? property.name.text : undefined;
90-
91-
return parseLitPropertyOption({ kind, initializer, config }, context);
92-
}, existingConfig);
93-
}
94-
95-
export function parseLitPropertyOption(
96-
{ kind, initializer, config }: { kind: string | undefined; initializer: Expression; config: LitElementPropertyConfig },
97-
context: AnalyzerVisitContext
98-
): LitElementPropertyConfig {
99-
const { ts, checker } = context;
100-
101-
// noinspection DuplicateCaseLabelJS
102-
switch (kind) {
103-
case "converter": {
104-
return { ...config, hasConverter: true };
122+
if (typeof object === "object" && object !== null && !Array.isArray(object)) {
123+
if (hasProperty(object, "converter") && object.converter !== undefined) {
124+
result.hasConverter = true;
105125
}
106126

107-
case "reflect": {
108-
return { ...config, reflect: resolveNodeValue(initializer, context)?.value === true };
127+
if (hasProperty(object, "reflect") && object.reflect !== undefined) {
128+
result.reflect = object.reflect === true;
109129
}
110130

111-
case "state": {
112-
return { ...config, state: resolveNodeValue(initializer, context)?.value === true };
131+
if (hasProperty(object, "state") && object.state !== undefined) {
132+
result.state = object.state === true;
113133
}
114134

115-
case "attribute": {
116-
let attribute: LitElementPropertyConfig["attribute"] | undefined;
117-
118-
if (initializer.kind === ts.SyntaxKind.TrueKeyword) {
119-
attribute = true;
120-
} else if (initializer.kind === ts.SyntaxKind.FalseKeyword) {
121-
attribute = false;
122-
} else if (ts.isStringLiteral(initializer)) {
123-
attribute = initializer.text;
124-
}
125-
126-
return {
127-
...config,
128-
attribute,
129-
node: {
130-
...(config.node || {}),
131-
attribute: initializer
132-
}
133-
};
135+
if (hasProperty(object, "value")) {
136+
result.default = object.value;
134137
}
135138

136-
case "type": {
137-
let type: LitElementPropertyConfig["type"] | undefined;
138-
const value = ts.isIdentifier(initializer) ? initializer.text : undefined;
139-
140-
switch (value) {
141-
case "String":
142-
case "StringConstructor":
143-
type = { kind: "STRING" };
144-
break;
145-
case "Number":
146-
case "NumberConstructor":
147-
type = { kind: "NUMBER" };
148-
break;
149-
case "Boolean":
150-
case "BooleanConstructor":
151-
type = { kind: "BOOLEAN" };
152-
break;
153-
case "Array":
154-
case "ArrayConstructor":
155-
type = { kind: "ARRAY", type: { kind: "ANY" } };
156-
break;
157-
case "Object":
158-
case "ObjectConstructor":
159-
type = { kind: "OBJECT", members: [] };
160-
break;
161-
default:
162-
// This is an unknown type, so set the name as a string
163-
type = initializer.getText();
164-
break;
165-
}
139+
if (hasProperty(object, "attribute") && (typeof object.attribute === "boolean" || typeof object.attribute === "string")) {
140+
result.attribute = object.attribute;
166141

167-
return {
168-
...config,
169-
type,
170-
node: {
171-
...(config.node || {}),
172-
type: initializer
142+
if (ts.isObjectLiteralExpression(node)) {
143+
const prop = node.properties.find(
144+
(p): p is PropertyAssignment => ts.isPropertyAssignment(p) && ts.isIdentifier(p.name) && p.name.text === "attribute"
145+
);
146+
if (prop) {
147+
attributeInitializer = prop.initializer;
173148
}
174-
};
149+
}
175150
}
151+
}
152+
153+
if (ts.isObjectLiteralExpression(node)) {
154+
const typeProp = node.properties.find(
155+
(p): p is PropertyAssignment => ts.isPropertyAssignment(p) && ts.isIdentifier(p.name) && p.name.text === "type"
156+
);
176157

177-
// Polymer specific field
178-
case "value": {
179-
return { ...config, default: resolveNodeValue(initializer, { ts, checker })?.value };
158+
if (typeProp) {
159+
typeInitializer = typeProp.initializer;
160+
result.type = getLitPropertyType(ts, typeProp.initializer);
180161
}
181162
}
182163

183-
return config;
164+
return {
165+
...result,
166+
node: {
167+
...(result.node || {}),
168+
attribute: attributeInitializer,
169+
type: typeInitializer
170+
}
171+
};
184172
}

test/flavors/lit-element/member-test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ tsTest("LitElement: Discovers properties from '@property'", t => {
5555
results: [result],
5656
checker
5757
} = analyzeTextWithCurrentTsModule(`
58+
const configVariable = {type: Number};
5859
/**
5960
* @element
6061
*/
@@ -67,6 +68,8 @@ tsTest("LitElement: Discovers properties from '@property'", t => {
6768
@property({attribute: false}) protected myProp2!: number;
6869
6970
@property() myProp3;
71+
72+
@property(configVariable) myProp4;
7073
}
7174
`);
7275

@@ -115,6 +118,19 @@ tsTest("LitElement: Discovers properties from '@property'", t => {
115118
deprecated: undefined,
116119
required: undefined,
117120
meta: {}
121+
},
122+
{
123+
kind: "property",
124+
propName: "myProp4",
125+
attrName: "myProp4",
126+
default: undefined,
127+
type: () => ({ kind: "NUMBER" }),
128+
visibility: "public",
129+
deprecated: undefined,
130+
required: undefined,
131+
meta: {
132+
type: { kind: "NUMBER" }
133+
}
118134
}
119135
],
120136
t,

0 commit comments

Comments
 (0)