Skip to content

Commit fa548cd

Browse files
authored
Add support for TypeScript 5.0 (#264)
* Install `typescript@~5.0`. * Add `5.0` to the set of tested TypeScript versions. * Support for TypeScript 4.8+ decorator AST changes (cherry-pick of #249) * Fall back to `isTypeAssertion` for `isTypeAssertionExpression` (introduced in `4.0.2`). (cherry-pick c69e804) * Fix building with TypeScript before `isTypeAssertionExpression` existed. * Remove indentation from JSDoc descriptions in tests. (...) Somewhere between 3.8 and 5.0, indentation for JSDoc description lines starting with whitespace was fixed to not include the additional space immediately after the line's leading `*`. The indentiation in this test causes it to fail in at least TS 5.0, so I'm removing it rather than trying to track down exactly when this change was made and then propagating the version number to the test. * `DocumentAndElementEventHandlers` was removed in microsoft/TypeScript#52328 (0c2fa39). * Revert back to `lockfileVersion: 1` by installing new deps with npm v6 instead of v9.
1 parent bf76b43 commit fa548cd

File tree

9 files changed

+77
-22
lines changed

9 files changed

+77
-22
lines changed

package-lock.json

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,8 @@
7373
"typescript-3.5": "npm:typescript@~3.5.3",
7474
"typescript-3.6": "npm:typescript@~3.6.4",
7575
"typescript-3.7": "npm:typescript@~3.7.4",
76-
"typescript-3.8": "npm:typescript@~3.8.0"
76+
"typescript-3.8": "npm:typescript@~3.8.0",
77+
"typescript-5.0": "npm:typescript@~5.0.3"
7778
},
7879
"ava": {
7980
"snapshotDir": "test/snapshots/results",

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Node } from "typescript";
22
import { AnalyzerVisitContext } from "../../analyzer-visit-context";
3-
import { getNodeIdentifier } from "../../util/ast-util";
3+
import { getDecorators, getNodeIdentifier } from "../../util/ast-util";
44
import { resolveNodeValue } from "../../util/resolve-node-value";
55
import { DefinitionNodeResult } from "../analyzer-flavor";
66

@@ -16,7 +16,7 @@ export function discoverDefinitions(node: Node, context: AnalyzerVisitContext):
1616
// @customElement("my-element")
1717
if (ts.isClassDeclaration(node)) {
1818
// Visit all decorators on the class
19-
for (const decorator of node.decorators || []) {
19+
for (const decorator of getDecorators(node, context)) {
2020
const callExpression = decorator.expression;
2121

2222
// Find "@customElement"

src/analyze/flavors/lit-element/parse-lit-property-configuration.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { CallExpression, Expression, Node, ObjectLiteralExpression } from "typescript";
22
import { AnalyzerVisitContext } from "../../analyzer-visit-context";
33
import { LitElementPropertyConfig } from "../../types/features/lit-element-property-config";
4+
import { getDecorators } from "../../util/ast-util";
45
import { resolveNodeValue } from "../../util/resolve-node-value";
56

67
export type LitElementPropertyDecoratorKind = "property" | "internalProperty";
@@ -16,11 +17,10 @@ export function getLitElementPropertyDecorator(
1617
node: Node,
1718
context: AnalyzerVisitContext
1819
): { expression: CallExpression; kind: LitElementPropertyDecoratorKind } | undefined {
19-
if (node.decorators == null) return undefined;
2020
const { ts } = context;
2121

2222
// Find a decorator with "property" name.
23-
for (const decorator of node.decorators) {
23+
for (const decorator of getDecorators(node, context)) {
2424
const expression = decorator.expression;
2525

2626
// We find the first decorator calling specific identifier name (found in LIT_ELEMENT_PROPERTY_DECORATOR_KINDS)
@@ -32,6 +32,8 @@ export function getLitElementPropertyDecorator(
3232
}
3333
}
3434
}
35+
36+
return undefined;
3537
}
3638

3739
/**

src/analyze/util/ast-util.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ import { isAssignableToSimpleTypeKind } from "ts-simple-type";
22
import * as tsModule from "typescript";
33
import {
44
Declaration,
5+
Decorator,
56
Identifier,
67
InterfaceDeclaration,
78
Node,
9+
NodeArray,
810
PropertyDeclaration,
911
PropertySignature,
1012
SetAccessorDeclaration,
@@ -347,3 +349,46 @@ export function getNodeIdentifier(node: Node, context: { ts: typeof tsModule }):
347349

348350
return undefined;
349351
}
352+
353+
/**
354+
* Returns all decorators in either the node's `decorators` or `modifiers`.
355+
* @param node
356+
* @param context
357+
*/
358+
export function getDecorators(node: Node, context: { ts: typeof tsModule }): ReadonlyArray<Decorator> {
359+
const { ts } = context;
360+
361+
// As of TypeScript 4.8 decorators have been moved from the `decorators`
362+
// property into `modifiers`. For compatibility with versions on both sides of
363+
// this change, this function first attempts to use the new utility functions
364+
// from TS 4.8+, otherwise it combines and filters all decorators found in
365+
// either `decorators` or `modifiers`.
366+
//
367+
// https://devblogs.microsoft.com/typescript/announcing-typescript-4-8/#decorators-are-placed-on-modifiers-on-typescripts-syntax-trees
368+
369+
// Declare enough of the TS 4.8+ API to let us check for and use these
370+
// functions despite compiling with an earlier version of TS.
371+
interface HasDecorators extends Node {
372+
_sentinel: never;
373+
}
374+
type TSModuleExports = typeof tsModule;
375+
interface TSWithDecorators extends TSModuleExports {
376+
canHaveDecorators: (node: Node) => node is HasDecorators;
377+
getDecorators: (node: HasDecorators) => ReadonlyArray<Decorator> | undefined;
378+
}
379+
380+
// Use TS 4.8+ functions if available.
381+
const isTSWithDecorators = (ts: typeof tsModule): ts is TSWithDecorators => typeof (ts as any).canHaveDecorators === "function";
382+
if (isTSWithDecorators(ts)) {
383+
return ts.canHaveDecorators(node) ? ts.getDecorators(node) ?? [] : [];
384+
}
385+
386+
// Fall back to manually checking `decorators` and `modifiers`.
387+
const decorators = Array.from((node.decorators ?? []) as NodeArray<Decorator>);
388+
for (const modifier of node.modifiers ?? []) {
389+
if (ts.isDecorator(modifier)) {
390+
decorators.push(modifier);
391+
}
392+
}
393+
return decorators;
394+
}

src/analyze/util/resolve-node-value.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ export interface Context {
99
strict?: boolean;
1010
}
1111

12+
interface TSMaybeWithIsTypeAssertionExpression {
13+
isTypeAssertionExpression?: typeof tsModule.isTypeAssertion;
14+
}
15+
1216
/**
1317
* Takes a node and tries to resolve a constant value from it.
1418
* Returns undefined if no constant value can be resolved.
@@ -107,7 +111,11 @@ export function resolveNodeValue(node: Node | undefined, context: Context): { va
107111
// - "my-value" as string
108112
// - <any>"my-value"
109113
// - ("my-value")
110-
else if (ts.isAsExpression(node) || ts.isTypeAssertion(node) || ts.isParenthesizedExpression(node)) {
114+
else if (
115+
ts.isAsExpression(node) ||
116+
((ts as TSMaybeWithIsTypeAssertionExpression).isTypeAssertionExpression ?? ts.isTypeAssertion)(node) ||
117+
ts.isParenthesizedExpression(node)
118+
) {
111119
return resolveNodeValue(node.expression, { ...context, depth });
112120
}
113121

test/flavors/custom-element/analyze-html-element-test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ tsTest("analyzeHTMLElement returns correct result", t => {
1616
// Test that the node extends some of the interfaces
1717
t.truthy(ext.has("EventTarget"));
1818
t.truthy(ext.has("Node"));
19-
t.truthy(ext.has("DocumentAndElementEventHandlers"));
19+
t.truthy(ext.has("HTMLOrSVGElement"));
2020
t.truthy(ext.has("ElementContentEditable"));
2121

2222
// From ElementContentEditable interface

test/flavors/jsdoc/description-test.ts

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,9 @@ tsTest("jsdoc: Correctly discovers the description in the jsdoc", t => {
66
results: [result]
77
} = analyzeTextWithCurrentTsModule(`
88
/**
9-
* layout to full document height as follows:
9+
* This CSS makes text in \`<p>\` tags red.
1010
* \`\`\`
11-
* \\@media screen {
12-
* html, body {
13-
* height: 100%;
14-
* }
15-
* }
11+
* p { color: red; }
1612
* \`\`\`
1713
* This is an example
1814
* @element
@@ -25,13 +21,9 @@ tsTest("jsdoc: Correctly discovers the description in the jsdoc", t => {
2521

2622
t.is(
2723
declaration.jsDoc?.description,
28-
`layout to full document height as follows:
24+
`This CSS makes text in \`<p>\` tags red.
2925
\`\`\`
30-
@media screen {
31-
html, body {
32-
height: 100%;
33-
}
34-
}
26+
p { color: red; }
3527
\`\`\`
3628
This is an example`
3729
);

test/helpers/ts-test.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ import * as tsModule from "typescript";
44

55
type TestFunction = (title: string, implementation: Implementation) => void;
66

7-
type TsModuleKind = "current" | "3.5" | "3.6" | "3.7" | "3.8";
7+
type TsModuleKind = "current" | "3.5" | "3.6" | "3.7" | "3.8" | "5.0";
88

9-
const TS_MODULES_ALL: TsModuleKind[] = ["current", "3.5", "3.6", "3.7", "3.8"];
9+
const TS_MODULES_ALL: TsModuleKind[] = ["current", "3.5", "3.6", "3.7", "3.8", "5.0"];
1010

11-
const TS_MODULES_DEFAULT: TsModuleKind[] = ["3.5", "3.6", "3.7", "3.8"];
11+
const TS_MODULES_DEFAULT: TsModuleKind[] = ["3.5", "3.6", "3.7", "3.8", "5.0"];
1212

1313
/**
1414
* Returns the name of the module to require for a specific ts module kind
@@ -21,6 +21,7 @@ function getTsModuleNameWithKind(kind: TsModuleKind | undefined): string {
2121
case "3.6":
2222
case "3.7":
2323
case "3.8":
24+
case "5.0":
2425
return `typescript-${kind}`;
2526
case "current":
2627
case undefined:

0 commit comments

Comments
 (0)