Skip to content

Commit 8fa35f2

Browse files
authoredNov 29, 2022
feat(typescript): support typescript 4.8 (#3743)
this commit updates typescript for stencil to v4.8.4. it leverages the work done in [#3748](#3748) (a043e5d) in order to facilitate the creation of nodes in the syntax tree that contain `ModifierLike` entities. it also leverages the work from [#3835](#3835) in order to provide testing to a more fickle aspect of the codebase. starting with typescript 4.8, decorators are not longer directly accessible on a syntax tree node. this is a result of the decorators proposal reaching stage 3, and soon to be implemented by the typescript team. in order to prepare for the implementation, where decorators are stored has changed. more information on this change can be found in https://devblogs.microsoft.com/typescript/announcing-typescript-4-8/#decorators-are-placed-on-modifiers-on-typescripts-syntax-trees. in order for decorators to be retrieved, typescript provides a series of helper functions to retrieve both decorators and modifiers off of a node. this pr introduces helper methods of our own that abstract over typescript's own. this is done to reduce some of the verbosity in attempting to retrieve decorators/modifiers off of a node. these functions return either the found decorators/modifiers on a node, `undefined` if the node can have a decorator/modifier but does not have any, or `undefined` if the node cannot have a decorator/modifier on it. this is an intentional design decision, as opposed to returning an empty array in place of `undefined` for the latter two cases. The reason for which is that there are cases in Stencil where an empty array and undefined have specific meaning that i did not feel was worth the additional effort in the context of this effort with typescript 4.8, several node factory methods no longer accept an explicit argument for decorators. instead, the parameters for decorators and modifiers have been coalesced into a single parameter of type `ModifierLike[]`. as a result, many calls to these factory functions with an argument of `undefined` for decorators have been updated to reflect this change
1 parent b221f8f commit 8fa35f2

32 files changed

+334
-221
lines changed
 

‎.github/dependabot.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ updates:
1515
- dependency-name: '@types/node'
1616
versions: ['17', '18']
1717
- dependency-name: 'typescript'
18-
versions: ['4.8']
18+
versions: ['>=4.9']
1919
# disable Jest updates until the new testing architecture is in place
2020
- dependency-name: '@types/jest'
2121
versions: ['>=28']

‎package-lock.json

+7-7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@
122122
"semver": "^7.3.7",
123123
"sizzle": "^2.3.6",
124124
"terser": "5.6.1",
125-
"typescript": "4.7.4",
125+
"typescript": "4.8.4",
126126
"webpack": "^4.46.0",
127127
"ws": "7.4.6"
128128
},

‎src/compiler/sys/dependencies.json

+1
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
"compiler/lib.es2022.full.d.ts",
6565
"compiler/lib.es2022.intl.d.ts",
6666
"compiler/lib.es2022.object.d.ts",
67+
"compiler/lib.es2022.sharedmemory.d.ts",
6768
"compiler/lib.es2022.string.d.ts",
6869
"compiler/lib.es5.d.ts",
6970
"compiler/lib.es6.d.ts",

‎src/compiler/transformers/add-component-meta-static.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import ts from 'typescript';
22

33
import type * as d from '../../declarations';
4-
import { convertValueToLiteral, createStaticGetter } from './transform-utils';
4+
import { convertValueToLiteral, createStaticGetter, retrieveModifierLike } from './transform-utils';
55

66
/**
77
* Update an instance of TypeScript's Intermediate Representation (IR) for a
@@ -24,8 +24,7 @@ export const addComponentMetaStatic = (
2424

2525
return ts.factory.updateClassDeclaration(
2626
cmpNode,
27-
cmpNode.decorators,
28-
cmpNode.modifiers,
27+
retrieveModifierLike(cmpNode),
2928
cmpNode.name,
3029
cmpNode.typeParameters,
3130
cmpNode.heritageClauses,

‎src/compiler/transformers/component-hydrate/hydrate-component.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { updateLazyComponentConstructor } from '../component-lazy/lazy-construct
55
import { addLazyElementGetter } from '../component-lazy/lazy-element-getter';
66
import { transformHostData } from '../host-data-transform';
77
import { removeStaticMetaProperties } from '../remove-static-meta-properties';
8+
import { retrieveModifierLike } from '../transform-utils';
89
import { addWatchers } from '../watcher-meta-transform';
910
import { addHydrateRuntimeCmpMeta } from './hydrate-runtime-cmp-meta';
1011

@@ -15,8 +16,7 @@ export const updateHydrateComponentClass = (
1516
) => {
1617
return ts.factory.updateClassDeclaration(
1718
classNode,
18-
classNode.decorators,
19-
classNode.modifiers,
19+
retrieveModifierLike(classNode),
2020
classNode.name,
2121
classNode.typeParameters,
2222
classNode.heritageClauses,

‎src/compiler/transformers/component-lazy/lazy-constructor.ts

+3-4
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@ import type * as d from '../../../declarations';
44
import { addCoreRuntimeApi, REGISTER_INSTANCE, RUNTIME_APIS } from '../core-runtime-apis';
55
import { addCreateEvents } from '../create-event';
66
import { addLegacyProps } from '../legacy-props';
7+
import { retrieveTsModifiers } from '../transform-utils';
78

89
export const updateLazyComponentConstructor = (
910
classMembers: ts.ClassElement[],
1011
moduleFile: d.Module,
1112
cmp: d.ComponentCompilerMeta
1213
) => {
1314
const cstrMethodArgs = [
14-
ts.factory.createParameterDeclaration(undefined, undefined, undefined, ts.factory.createIdentifier(HOST_REF_ARG)),
15+
ts.factory.createParameterDeclaration(undefined, undefined, ts.factory.createIdentifier(HOST_REF_ARG)),
1516
];
1617

1718
const cstrMethodIndex = classMembers.findIndex((m) => m.kind === ts.SyntaxKind.Constructor);
@@ -28,15 +29,13 @@ export const updateLazyComponentConstructor = (
2829

2930
classMembers[cstrMethodIndex] = ts.factory.updateConstructorDeclaration(
3031
cstrMethod,
31-
cstrMethod.decorators,
32-
cstrMethod.modifiers,
32+
retrieveTsModifiers(cstrMethod),
3333
cstrMethodArgs,
3434
body
3535
);
3636
} else {
3737
// create a constructor()
3838
const cstrMethod = ts.factory.createConstructorDeclaration(
39-
undefined,
4039
undefined,
4140
cstrMethodArgs,
4241
ts.factory.createBlock(

‎src/compiler/transformers/component-lazy/lazy-element-getter.ts

-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ export const addLazyElementGetter = (
1616

1717
classMembers.push(
1818
ts.factory.createGetAccessorDeclaration(
19-
undefined,
2019
undefined,
2120
cmp.elementRef,
2221
[],

‎src/compiler/transformers/component-native/add-define-custom-element-function.ts

+1-3
Original file line numberDiff line numberDiff line change
@@ -154,12 +154,11 @@ const addDefineCustomElementFunction = (
154154
caseStatements: ts.CaseClause[]
155155
) => {
156156
const newExpression = ts.factory.createFunctionDeclaration(
157-
undefined,
158157
[ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)],
159158
undefined,
160159
ts.factory.createIdentifier('defineCustomElement'),
161160
undefined,
162-
undefined,
161+
[],
163162
undefined,
164163
ts.factory.createBlock(
165164
[
@@ -196,7 +195,6 @@ const addDefineCustomElementFunction = (
196195
undefined,
197196
[
198197
ts.factory.createParameterDeclaration(
199-
undefined,
200198
undefined,
201199
undefined,
202200
ts.factory.createIdentifier('tagName'),

‎src/compiler/transformers/component-native/native-connected-callback.ts

+2-4
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,12 @@ export const addNativeConnectedCallback = (classMembers: ts.ClassElement[], cmp:
2424
if (connectedCallback != null) {
2525
// class already has a connectedCallback(), so update it
2626
const callbackMethod = ts.factory.createMethodDeclaration(
27-
undefined,
2827
undefined,
2928
undefined,
3029
'connectedCallback',
3130
undefined,
3231
undefined,
33-
undefined,
32+
[],
3433
undefined,
3534
ts.factory.createBlock([fnCall, ...connectedCallback.body.statements], true)
3635
);
@@ -39,13 +38,12 @@ export const addNativeConnectedCallback = (classMembers: ts.ClassElement[], cmp:
3938
} else {
4039
// class doesn't have a connectedCallback(), so add it
4140
const callbackMethod = ts.factory.createMethodDeclaration(
42-
undefined,
4341
undefined,
4442
undefined,
4543
'connectedCallback',
4644
undefined,
4745
undefined,
48-
undefined,
46+
[],
4947
undefined,
5048
ts.factory.createBlock([fnCall], true)
5149
);

‎src/compiler/transformers/component-native/native-constructor.ts

+3-8
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type * as d from '../../../declarations';
44
import { addCoreRuntimeApi, RUNTIME_APIS } from '../core-runtime-apis';
55
import { addCreateEvents } from '../create-event';
66
import { addLegacyProps } from '../legacy-props';
7+
import { retrieveTsModifiers } from '../transform-utils';
78

89
export const updateNativeConstructor = (
910
classMembers: ts.ClassElement[],
@@ -36,8 +37,7 @@ export const updateNativeConstructor = (
3637

3738
classMembers[cstrMethodIndex] = ts.factory.updateConstructorDeclaration(
3839
cstrMethod,
39-
cstrMethod.decorators,
40-
cstrMethod.modifiers,
40+
retrieveTsModifiers(cstrMethod),
4141
cstrMethod.parameters,
4242
ts.factory.updateBlock(cstrMethod.body, statements)
4343
);
@@ -53,12 +53,7 @@ export const updateNativeConstructor = (
5353
statements = [createNativeConstructorSuper(), ...statements];
5454
}
5555

56-
const cstrMethod = ts.factory.createConstructorDeclaration(
57-
undefined,
58-
undefined,
59-
undefined,
60-
ts.factory.createBlock(statements, true)
61-
);
56+
const cstrMethod = ts.factory.createConstructorDeclaration(undefined, [], ts.factory.createBlock(statements, true));
6257
classMembers.unshift(cstrMethod);
6358
}
6459
};

‎src/compiler/transformers/component-native/native-element-getter.ts

-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ export const addNativeElementGetter = (classMembers: ts.ClassElement[], cmp: d.C
99
if (cmp.elementRef) {
1010
classMembers.push(
1111
ts.factory.createGetAccessorDeclaration(
12-
undefined,
1312
undefined,
1413
cmp.elementRef,
1514
[],

‎src/compiler/transformers/decorators-to-static/component-decorator.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { augmentDiagnosticWithNode, buildError, buildWarn, isString, validateCom
22
import ts from 'typescript';
33

44
import type * as d from '../../../declarations';
5-
import { convertValueToLiteral, createStaticGetter } from '../transform-utils';
5+
import { convertValueToLiteral, createStaticGetter, retrieveTsDecorators } from '../transform-utils';
66
import { getDeclarationParameters } from './decorator-utils';
77
import { styleToStatic } from './style-to-static';
88

@@ -88,7 +88,7 @@ const validateComponent = (
8888
}
8989

9090
// check if class has more than one decorator
91-
const otherDecorator = cmpNode.decorators && cmpNode.decorators.find((d) => d !== componentDecorator);
91+
const otherDecorator = retrieveTsDecorators(cmpNode)?.find((d) => d !== componentDecorator);
9292
if (otherDecorator) {
9393
const err = buildError(diagnostics);
9494
err.messageText = `Classes decorated with @Component can not be decorated with more decorators.

‎src/compiler/transformers/decorators-to-static/convert-decorators.ts

+22-24
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { augmentDiagnosticWithNode, buildError } from '@utils';
22
import ts from 'typescript';
33

44
import type * as d from '../../../declarations';
5+
import { retrieveTsDecorators, retrieveTsModifiers } from '../transform-utils';
56
import { componentDecoratorToStatic } from './component-decorator';
67
import { isDecoratorNamed } from './decorator-utils';
78
import {
@@ -42,19 +43,13 @@ const visitClassDeclaration = (
4243
typeChecker: ts.TypeChecker,
4344
classNode: ts.ClassDeclaration
4445
) => {
45-
if (!classNode.decorators) {
46-
return classNode;
47-
}
48-
49-
const componentDecorator = classNode.decorators.find(isDecoratorNamed('Component'));
46+
const componentDecorator = retrieveTsDecorators(classNode)?.find(isDecoratorNamed('Component'));
5047
if (!componentDecorator) {
5148
return classNode;
5249
}
5350

5451
const classMembers = classNode.members;
55-
const decoratedMembers = classMembers.filter(
56-
(member) => Array.isArray(member.decorators) && member.decorators.length > 0
57-
);
52+
const decoratedMembers = classMembers.filter((member) => (retrieveTsDecorators(member)?.length ?? 0) > 0);
5853

5954
// create an array of all class members which are _not_ methods decorated
6055
// with a Stencil decorator. We do this here because we'll implement the
@@ -84,10 +79,13 @@ const visitClassDeclaration = (
8479

8580
validateMethods(diagnostics, classMembers);
8681

82+
const currentDecorators = retrieveTsDecorators(classNode);
8783
return ts.factory.updateClassDeclaration(
8884
classNode,
89-
filterDecorators(classNode.decorators, CLASS_DECORATORS_TO_REMOVE),
90-
classNode.modifiers,
85+
[
86+
...(filterDecorators(currentDecorators, CLASS_DECORATORS_TO_REMOVE) ?? []),
87+
...(retrieveTsModifiers(classNode) ?? []),
88+
],
9189
classNode.name,
9290
classNode.typeParameters,
9391
classNode.heritageClauses,
@@ -116,15 +114,14 @@ const removeStencilMethodDecorators = (
116114
diagnostics: d.Diagnostic[]
117115
): ts.ClassElement[] => {
118116
return classMembers.map((member) => {
119-
const currentDecorators = member.decorators;
120-
const newDecorators = filterDecorators(member.decorators, MEMBER_DECORATORS_TO_REMOVE);
117+
const currentDecorators = retrieveTsDecorators(member);
118+
const newDecorators = filterDecorators(currentDecorators, MEMBER_DECORATORS_TO_REMOVE);
121119

122120
if (currentDecorators !== newDecorators) {
123121
if (ts.isMethodDeclaration(member)) {
124122
return ts.factory.updateMethodDeclaration(
125123
member,
126-
newDecorators,
127-
member.modifiers,
124+
[...(newDecorators ?? []), ...(retrieveTsModifiers(member) ?? [])],
128125
member.asteriskToken,
129126
member.name,
130127
member.questionToken,
@@ -143,10 +140,10 @@ const removeStencilMethodDecorators = (
143140
return member;
144141
} else {
145142
// update the property to remove decorators
143+
const modifiers = retrieveTsModifiers(member);
146144
return ts.factory.updatePropertyDeclaration(
147145
member,
148-
newDecorators,
149-
member.modifiers,
146+
[...(newDecorators ?? []), ...(modifiers ?? [])],
150147
member.name,
151148
member.questionToken,
152149
member.type,
@@ -173,9 +170,9 @@ const removeStencilMethodDecorators = (
173170
* - the node contains only decorators in the provided list
174171
*/
175172
export const filterDecorators = (
176-
decorators: ts.NodeArray<ts.Decorator>,
173+
decorators: ReadonlyArray<ts.Decorator> | undefined,
177174
excludeList: ReadonlyArray<string>
178-
): ts.NodeArray<ts.Decorator> | undefined => {
175+
): ReadonlyArray<ts.Decorator> | undefined => {
179176
if (decorators) {
180177
const updatedDecoratorList = decorators.filter((dec) => {
181178
// narrow the type of the syntax tree node, while retrieving the text of the identifier
@@ -196,6 +193,7 @@ export const filterDecorators = (
196193
return ts.factory.createNodeArray(updatedDecoratorList);
197194
}
198195
}
196+
199197
// return the node's original decorators, or undefined
200198
return decorators;
201199
};
@@ -401,8 +399,7 @@ export const updateConstructor = (
401399

402400
classMembers[constructorIndex] = ts.factory.updateConstructorDeclaration(
403401
constructorMethod,
404-
constructorMethod.decorators,
405-
constructorMethod.modifiers,
402+
retrieveTsModifiers(constructorMethod),
406403
constructorMethod.parameters,
407404
ts.factory.updateBlock(constructorMethod?.body ?? ts.factory.createBlock([]), statements)
408405
);
@@ -414,7 +411,7 @@ export const updateConstructor = (
414411
}
415412

416413
classMembers = [
417-
ts.factory.createConstructorDeclaration(undefined, undefined, [], ts.factory.createBlock(statements, true)),
414+
ts.factory.createConstructorDeclaration(undefined, [], ts.factory.createBlock(statements, true)),
418415
...classMembers,
419416
];
420417
}
@@ -465,11 +462,12 @@ const createConstructorBodyWithSuper = (): ts.ExpressionStatement => {
465462
* @returns whether this should be rewritten or not
466463
*/
467464
const shouldInitializeInConstructor = (member: ts.ClassElement): boolean => {
468-
const filteredDecorators = filterDecorators(member.decorators, CONSTRUCTOR_DEFINED_MEMBER_DECORATORS);
469-
if (member.decorators === undefined) {
465+
const currentDecorators = retrieveTsDecorators(member);
466+
if (currentDecorators === undefined) {
470467
// decorators have already been removed from this element, indicating that
471468
// we don't need to do anything
472469
return false;
473470
}
474-
return member.decorators !== filteredDecorators;
471+
const filteredDecorators = filterDecorators(currentDecorators, CONSTRUCTOR_DEFINED_MEMBER_DECORATORS);
472+
return currentDecorators !== filteredDecorators;
475473
};

‎src/compiler/transformers/decorators-to-static/element-decorator.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { buildError } from '@utils';
22
import ts from 'typescript';
33

44
import type * as d from '../../../declarations';
5-
import { createStaticGetter } from '../transform-utils';
5+
import { createStaticGetter, retrieveTsDecorators } from '../transform-utils';
66
import { isDecoratorNamed } from './decorator-utils';
77

88
export const elementDecoratorsToStatic = (
@@ -29,8 +29,8 @@ const parseElementDecorator = (
2929
_diagnostics: d.Diagnostic[],
3030
_typeChecker: ts.TypeChecker,
3131
prop: ts.PropertyDeclaration
32-
) => {
33-
const elementDecorator = prop.decorators && prop.decorators.find(isDecoratorNamed('Element'));
32+
): string | null => {
33+
const elementDecorator = retrieveTsDecorators(prop)?.find(isDecoratorNamed('Element'));
3434

3535
if (elementDecorator == null) {
3636
return null;

‎src/compiler/transformers/decorators-to-static/event-decorator.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
createStaticGetter,
88
getAttributeTypeInfo,
99
resolveType,
10+
retrieveTsDecorators,
1011
serializeSymbol,
1112
validateReferences,
1213
} from '../transform-utils';
@@ -43,7 +44,7 @@ const parseEventDecorator = (
4344
typeChecker: ts.TypeChecker,
4445
prop: ts.PropertyDeclaration
4546
): d.ComponentCompilerStaticEvent | null => {
46-
const eventDecorator = prop.decorators?.find(isDecoratorNamed('Event'));
47+
const eventDecorator = retrieveTsDecorators(prop)?.find(isDecoratorNamed('Event'));
4748

4849
if (eventDecorator == null) {
4950
return null;

‎src/compiler/transformers/decorators-to-static/listen-decorator.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { augmentDiagnosticWithNode, buildError, flatOne } from '@utils';
22
import ts from 'typescript';
33

44
import type * as d from '../../../declarations';
5-
import { convertValueToLiteral, createStaticGetter } from '../transform-utils';
5+
import { convertValueToLiteral, createStaticGetter, retrieveTsDecorators } from '../transform-utils';
66
import { getDeclarationParameters, isDecoratorNamed } from './decorator-utils';
77

88
export const listenDecoratorsToStatic = (
@@ -20,8 +20,11 @@ export const listenDecoratorsToStatic = (
2020
}
2121
};
2222

23-
const parseListenDecorators = (diagnostics: d.Diagnostic[], method: ts.MethodDeclaration) => {
24-
const listenDecorators = method.decorators.filter(isDecoratorNamed('Listen'));
23+
const parseListenDecorators = (
24+
diagnostics: d.Diagnostic[],
25+
method: ts.MethodDeclaration
26+
): d.ComponentCompilerListener[] => {
27+
const listenDecorators = (retrieveTsDecorators(method) ?? []).filter(isDecoratorNamed('Listen'));
2528
if (listenDecorators.length === 0) {
2629
return [];
2730
}

‎src/compiler/transformers/decorators-to-static/method-decorator.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import {
99
getAttributeTypeInfo,
1010
isMemberPrivate,
1111
mapJSDocTagInfo,
12+
retrieveTsDecorators,
13+
retrieveTsModifiers,
1214
serializeSymbol,
1315
typeToString,
1416
validateReferences,
@@ -40,8 +42,8 @@ const parseMethodDecorator = (
4042
tsSourceFile: ts.SourceFile,
4143
typeChecker: ts.TypeChecker,
4244
method: ts.MethodDeclaration
43-
) => {
44-
const methodDecorator = method.decorators.find(isDecoratorNamed('Method'));
45+
): ts.PropertyAssignment | null => {
46+
const methodDecorator = retrieveTsDecorators(method)?.find(isDecoratorNamed('Method'));
4547
if (methodDecorator == null) {
4648
return null;
4749
}
@@ -79,7 +81,7 @@ const parseMethodDecorator = (
7981
const err = buildError(diagnostics);
8082
err.messageText =
8183
'Methods decorated with the @Method() decorator cannot be "private" nor "protected". More info: https://stenciljs.com/docs/methods';
82-
augmentDiagnosticWithNode(err, method.modifiers[0]);
84+
augmentDiagnosticWithNode(err, retrieveTsModifiers(method)![0]);
8385
}
8486

8587
// Validate if the method name does not conflict with existing public names

‎src/compiler/transformers/decorators-to-static/prop-decorator.ts

+6-4
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import {
99
getAttributeTypeInfo,
1010
isMemberPrivate,
1111
resolveType,
12+
retrieveTsDecorators,
13+
retrieveTsModifiers,
1214
serializeSymbol,
1315
typeToString,
1416
validateReferences,
@@ -37,7 +39,7 @@ export const propDecoratorsToStatic = (
3739
const properties = decoratedProps
3840
.filter(ts.isPropertyDeclaration)
3941
.map((prop) => parsePropDecorator(diagnostics, typeChecker, prop, watchable))
40-
.filter((prop) => prop != null);
42+
.filter((prop): prop is ts.PropertyAssignment => prop != null);
4143

4244
if (properties.length > 0) {
4345
newMembers.push(createStaticGetter('properties', ts.factory.createObjectLiteralExpression(properties, true)));
@@ -58,8 +60,8 @@ const parsePropDecorator = (
5860
typeChecker: ts.TypeChecker,
5961
prop: ts.PropertyDeclaration,
6062
watchable: Set<string>
61-
): ts.PropertyAssignment => {
62-
const propDecorator = prop.decorators.find(isDecoratorNamed('Prop'));
63+
): ts.PropertyAssignment | null => {
64+
const propDecorator = retrieveTsDecorators(prop)?.find(isDecoratorNamed('Prop'));
6365
if (propDecorator == null) {
6466
return null;
6567
}
@@ -73,7 +75,7 @@ const parsePropDecorator = (
7375
const err = buildError(diagnostics);
7476
err.messageText =
7577
'Properties decorated with the @Prop() decorator cannot be "private" nor "protected". More info: https://stenciljs.com/docs/properties';
76-
augmentDiagnosticWithNode(err, prop.modifiers[0]);
78+
augmentDiagnosticWithNode(err, retrieveTsModifiers(prop)![0]);
7779
}
7880

7981
if (/^on(-|[A-Z])/.test(propName)) {

‎src/compiler/transformers/decorators-to-static/state-decorator.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import ts from 'typescript';
22

3-
import { createStaticGetter } from '../transform-utils';
3+
import { createStaticGetter, retrieveTsDecorators } from '../transform-utils';
44
import { isDecoratorNamed } from './decorator-utils';
55

66
/**
@@ -22,7 +22,7 @@ export const stateDecoratorsToStatic = (
2222
const states = decoratedProps
2323
.filter(ts.isPropertyDeclaration)
2424
.map((prop) => stateDecoratorToStatic(prop, watchable))
25-
.filter((state) => !!state);
25+
.filter((state): state is ts.PropertyAssignment => !!state);
2626

2727
if (states.length > 0) {
2828
newMembers.push(createStaticGetter('states', ts.factory.createObjectLiteralExpression(states, true)));
@@ -43,7 +43,7 @@ export const stateDecoratorsToStatic = (
4343
* prop to an empty object
4444
*/
4545
const stateDecoratorToStatic = (prop: ts.PropertyDeclaration, watchable: Set<string>): ts.PropertyAssignment | null => {
46-
const stateDecorator = prop.decorators.find(isDecoratorNamed('State'));
46+
const stateDecorator = retrieveTsDecorators(prop)?.find(isDecoratorNamed('State'));
4747
if (stateDecorator == null) {
4848
return null;
4949
}

‎src/compiler/transformers/decorators-to-static/watch-decorator.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { augmentDiagnosticWithNode, buildError, buildWarn, flatOne } from '@util
22
import ts from 'typescript';
33

44
import type * as d from '../../../declarations';
5-
import { convertValueToLiteral, createStaticGetter } from '../transform-utils';
5+
import { convertValueToLiteral, createStaticGetter, retrieveTsDecorators } from '../transform-utils';
66
import { getDeclarationParameters, isDecoratorNamed } from './decorator-utils';
77

88
export const watchDecoratorsToStatic = (
@@ -30,7 +30,8 @@ const parseWatchDecorator = (
3030
method: ts.MethodDeclaration
3131
): d.ComponentCompilerWatch[] => {
3232
const methodName = method.name.getText();
33-
return method.decorators.filter(isDecoratorNamed('Watch')).map((decorator) => {
33+
const decorators = retrieveTsDecorators(method) ?? [];
34+
return decorators.filter(isDecoratorNamed('Watch')).map((decorator) => {
3435
const [propName] = getDeclarationParameters<string>(decorator);
3536
if (!watchable.has(propName)) {
3637
const diagnostic = config.devMode ? buildWarn(diagnostics) : buildError(diagnostics);

‎src/compiler/transformers/host-data-transform.ts

+3-4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import ts from 'typescript';
22

33
import type * as d from '../../declarations';
44
import { addCoreRuntimeApi, H, HOST, RUNTIME_APIS } from './core-runtime-apis';
5+
import { retrieveModifierLike } from './transform-utils';
56

67
export const transformHostData = (classElements: ts.ClassElement[], moduleFile: d.Module) => {
78
const hasHostData = classElements.some(
@@ -15,8 +16,7 @@ export const transformHostData = (classElements: ts.ClassElement[], moduleFile:
1516
const renderMethod = classElements[renderIndex] as ts.MethodDeclaration;
1617
classElements[renderIndex] = ts.factory.updateMethodDeclaration(
1718
renderMethod,
18-
renderMethod.decorators,
19-
renderMethod.modifiers,
19+
retrieveModifierLike(renderMethod),
2020
renderMethod.asteriskToken,
2121
ts.factory.createIdentifier(INTERNAL_RENDER),
2222
renderMethod.questionToken,
@@ -61,13 +61,12 @@ const syntheticRender = (moduleFile: d.Module, hasRender: boolean) => {
6161
* }
6262
*/
6363
return ts.factory.createMethodDeclaration(
64-
undefined,
6564
undefined,
6665
undefined,
6766
'render',
6867
undefined,
6968
undefined,
70-
undefined,
69+
[],
7170
undefined,
7271
ts.factory.createBlock([
7372
ts.factory.createReturnStatement(

‎src/compiler/transformers/map-imports-to-path-aliases.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { dirname, relative } from 'path';
33
import ts from 'typescript';
44

55
import type * as d from '../../declarations';
6+
import { retrieveTsModifiers } from './transform-utils';
67

78
/**
89
* This method is responsible for replacing user-defined import path aliases ({@link https://www.typescriptlang.org/docs/handbook/module-resolution.html#path-mapping})
@@ -78,8 +79,7 @@ export const mapImportsToPathAliases = (
7879

7980
return transformCtx.factory.updateImportDeclaration(
8081
node,
81-
node.decorators,
82-
node.modifiers,
82+
retrieveTsModifiers(node),
8383
node.importClause,
8484
transformCtx.factory.createStringLiteral(importPath),
8585
node.assertClause

‎src/compiler/transformers/remove-static-meta-properties.ts

+7-7
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
import ts from 'typescript';
22

3-
export const removeStaticMetaProperties = (classNode: ts.ClassDeclaration) => {
3+
import { retrieveTsModifiers } from './transform-utils';
4+
5+
export const removeStaticMetaProperties = (classNode: ts.ClassDeclaration): ts.ClassElement[] => {
46
if (classNode.members == null) {
57
return [];
68
}
79
return classNode.members.filter((classMember) => {
8-
if (classMember.modifiers) {
9-
if (classMember.modifiers.some((m) => m.kind === ts.SyntaxKind.StaticKeyword)) {
10-
const memberName = (classMember.name as any).escapedText;
11-
if (REMOVE_STATIC_GETTERS.has(memberName)) {
12-
return false;
13-
}
10+
if (retrieveTsModifiers(classMember)?.some((m) => m.kind === ts.SyntaxKind.StaticKeyword)) {
11+
const memberName = (classMember.name as any).escapedText;
12+
if (REMOVE_STATIC_GETTERS.has(memberName)) {
13+
return false;
1414
}
1515
}
1616
return true;

‎src/compiler/transformers/style-imports.ts

+3-4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import ts from 'typescript';
22

33
import type * as d from '../../declarations';
44
import { serializeImportPath } from './stencil-import-path';
5+
import { retrieveTsModifiers } from './transform-utils';
56

67
export const updateStyleImports = (
78
transformOpts: d.TransformOptions,
@@ -63,7 +64,7 @@ const updateEsmStyleImportPath = (
6364
statements: ts.Statement[],
6465
cmp: d.ComponentCompilerMeta,
6566
style: d.StyleCompiler
66-
) => {
67+
): ts.Statement[] => {
6768
for (let i = 0; i < statements.length; i++) {
6869
const n = statements[i];
6970
if (ts.isImportDeclaration(n) && n.importClause && n.moduleSpecifier && ts.isStringLiteral(n.moduleSpecifier)) {
@@ -73,8 +74,7 @@ const updateEsmStyleImportPath = (
7374

7475
statements[i] = ts.factory.updateImportDeclaration(
7576
n,
76-
n.decorators,
77-
n.modifiers,
77+
retrieveTsModifiers(n),
7878
n.importClause,
7979
ts.factory.createStringLiteral(importPath),
8080
undefined
@@ -96,7 +96,6 @@ const createEsmStyleImport = (
9696
const importPath = getStyleImportPath(transformOpts, tsSourceFile, cmp, style, style.externalStyles[0].absolutePath);
9797

9898
return ts.factory.createImportDeclaration(
99-
undefined,
10099
undefined,
101100
ts.factory.createImportClause(false, importName, undefined),
102101
ts.factory.createStringLiteral(importPath)

‎src/compiler/transformers/test/add-component-meta-proxy.spec.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,11 @@ describe('add-component-meta-proxy', () => {
2828
]);
2929

3030
classExpr = ts.factory.createClassExpression(
31-
undefined,
3231
undefined,
3332
'MyComponent',
3433
undefined,
3534
[htmlElementHeritageClause],
36-
undefined
35+
[]
3736
);
3837
literalMetadata = ts.factory.createStringLiteral('MyComponent');
3938

‎src/compiler/transformers/test/convert-decorators.spec.ts

+24-58
Original file line numberDiff line numberDiff line change
@@ -334,68 +334,36 @@ describe('convert-decorators', () => {
334334
});
335335

336336
describe('filterDecorators', () => {
337-
/**
338-
* Create a decorated class member, 'classMemberVariable: string;', as it would exist in a class. No class
339-
* declaration is created.
340-
*
341-
* The property declaration will have any decorators provided applied to it.
342-
*
343-
* @example
344-
* // By default, no decorators will be applied, and the following will be returned:
345-
* createClassMemberWithDecorators(); // Node representing 'classMemberVariable: string;'
346-
*
347-
* // Otherwise, the provided modifier will be applied to the method:
348-
* const propDecorator = ts.factory.createDecorator(
349-
* ts.factory.createCallExpression(ts.factory.createIdentifier('Prop'), undefined, [])
350-
* );
351-
* createClassMemberWithDecorators([propDecorator]); // '@Prop() classMemberVariable: string;'
352-
*
353-
* @param decorators the decorators to apply to the property declaration
354-
* @returns the created property declaration
355-
*/
356-
const createClassMemberWithDecorators = (decorators?: ReadonlyArray<ts.Decorator>): ts.PropertyDeclaration => {
357-
const decoratorsToUse = decorators ? [...decorators] : undefined;
358-
return ts.factory.createPropertyDeclaration(
359-
decoratorsToUse,
360-
undefined,
361-
ts.factory.createIdentifier('classMemberVariable'),
362-
undefined,
363-
ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
364-
undefined
365-
);
366-
};
367-
368-
it("returns undefined if a node doesn't have any decorators", () => {
369-
// create a class member, 'classMemberVariable: string;', as it would exist in a class
370-
const member = createClassMemberWithDecorators(undefined);
371-
372-
const filteredDecorators = filterDecorators(member.decorators, []);
373-
374-
expect(filteredDecorators).toBeUndefined();
375-
});
337+
it.each<ReadonlyArray<ReadonlyArray<string>>>([[[]], [['ExcludedDecorator']]])(
338+
'returns undefined when no decorators are provided',
339+
(excludeList: ReadonlyArray<string>) => {
340+
const filteredDecorators = filterDecorators(undefined, excludeList);
376341

377-
it('returns undefined if a node has an empty list of decorators', () => {
378-
// create a class member, 'classMemberVariable: string;', as it would exist in a class
379-
const member = createClassMemberWithDecorators([]);
342+
expect(filteredDecorators).toBeUndefined();
343+
}
344+
);
380345

381-
const filteredDecorators = filterDecorators(member.decorators, []);
346+
it.each<ReadonlyArray<ReadonlyArray<string>>>([[[]], [['ExcludedDecorator']]])(
347+
'returns undefined for an empty list of decorators',
348+
(excludeList: ReadonlyArray<string>) => {
349+
const filteredDecorators = filterDecorators([], excludeList);
382350

383-
expect(filteredDecorators).toBeUndefined();
384-
});
351+
expect(filteredDecorators).toBeUndefined();
352+
}
353+
);
385354

386-
it("returns a node's decorator if it is not a call expression", () => {
387-
// create a decorated class member, '@Decorator classMemberVariable: string;', as it would exist in a class
388-
// note the lack of '()' after the decorator, making it an identifier expression, rather than a class expression
355+
it('returns a decorator if it is not a call expression', () => {
356+
// create a decorator, '@Decorator'. note the lack of '()' after the decorator, making it an identifier
357+
// expression, rather than a call expression
389358
const decorator = ts.factory.createDecorator(ts.factory.createIdentifier('Decorator'));
390-
const member = createClassMemberWithDecorators([decorator]);
391359

392-
const filteredDecorators = filterDecorators(member.decorators, []);
360+
const filteredDecorators = filterDecorators([decorator], []);
393361

394362
expect(filteredDecorators).toHaveLength(1);
395-
expect(filteredDecorators[0]).toBe(decorator);
363+
expect(filteredDecorators![0]).toBe(decorator);
396364
});
397365

398-
it("doesn't return any decorators when all decorators on a node exist in the exclude list", () => {
366+
it("doesn't return any decorators when all decorators in the exclude list", () => {
399367
// create a '@CustomProp()' decorator
400368
const customDecorator = ts.factory.createDecorator(
401369
ts.factory.createCallExpression(ts.factory.createIdentifier('CustomProp'), undefined, [])
@@ -404,14 +372,13 @@ describe('convert-decorators', () => {
404372
const decorator = ts.factory.createDecorator(
405373
ts.factory.createCallExpression(ts.factory.createIdentifier('Prop'), undefined, [])
406374
);
407-
const member = createClassMemberWithDecorators([customDecorator, decorator]);
408375

409-
const filteredDecorators = filterDecorators(member.decorators, ['Prop', 'CustomProp']);
376+
const filteredDecorators = filterDecorators([customDecorator, decorator], ['Prop', 'CustomProp']);
410377

411378
expect(filteredDecorators).toBeUndefined();
412379
});
413380

414-
it('returns any decorators on a node, not in the exclude list', () => {
381+
it('returns any decorators not in the exclude list', () => {
415382
// create a '@CustomProp()' decorator
416383
const customDecorator = ts.factory.createDecorator(
417384
ts.factory.createCallExpression(ts.factory.createIdentifier('CustomProp'), undefined, [])
@@ -420,12 +387,11 @@ describe('convert-decorators', () => {
420387
const decorator = ts.factory.createDecorator(
421388
ts.factory.createCallExpression(ts.factory.createIdentifier('Prop'), undefined, [])
422389
);
423-
const member = createClassMemberWithDecorators([customDecorator, decorator]);
424390

425-
const filteredDecorators = filterDecorators(member.decorators, ['Prop']);
391+
const filteredDecorators = filterDecorators([customDecorator, decorator], ['Prop']);
426392

427393
expect(filteredDecorators).toHaveLength(1);
428-
expect(filteredDecorators[0]).toBe(customDecorator);
394+
expect(filteredDecorators![0]).toBe(customDecorator);
429395
});
430396
});
431397
});
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
import * as ts from 'typescript';
22

3-
import { isMemberPrivate, mapJSDocTagInfo } from '../transform-utils';
3+
import {
4+
isMemberPrivate,
5+
mapJSDocTagInfo,
6+
retrieveModifierLike,
7+
retrieveTsDecorators,
8+
retrieveTsModifiers,
9+
} from '../transform-utils';
410

5-
describe('transform utils', () => {
11+
describe('transform-utils', () => {
612
it('flattens TypeScript JSDocTagInfo to Stencil JSDocTagInfo', () => {
713
// tags corresponds to the following JSDoc
814
/*
@@ -41,43 +47,43 @@ describe('transform utils', () => {
4147
]);
4248
});
4349

44-
describe('isMemberPrivate', () => {
45-
/**
46-
* Helper method for creating an empty method named 'myMethod' with the provided modifier's.
47-
*
48-
* @example
49-
* // By default, no modifier will be applied, and the following will be returned:
50-
* createMemberWithModifier(); // myMethod() {}
51-
*
52-
* // Otherwise, the provided modifier will be applied to the method:
53-
* createMemberWithModifier(ts.factory.createModifier(ts.SyntaxKind.PrivateKeyword)); // private myMethod() {}
54-
*
55-
* @param modifier the modifier to apply to the method. Defaults to applying no modifier if none is provided
56-
* @returns a new empty method
57-
*/
58-
const createMemberWithModifier = (modifier: ts.Modifier | undefined = undefined): ts.MethodDeclaration => {
59-
const modifiers = modifier ? [modifier] : [];
60-
return ts.factory.createMethodDeclaration(
61-
undefined,
62-
modifiers,
63-
undefined,
64-
ts.factory.createIdentifier('myMethod'),
65-
undefined,
66-
undefined,
67-
[],
68-
undefined,
69-
ts.factory.createBlock([], false)
70-
);
71-
};
50+
/**
51+
* Helper method for creating an empty method named 'myMethod' with the provided modifiers.
52+
*
53+
* @example
54+
* // By default, no modifiers will be applied, and the following will be returned:
55+
* createMemberWithModifiers(); // myMethod() {}
56+
*
57+
* // Otherwise, the provided modifiers will be applied to the method:
58+
* createMemberWithModifiers([ts.factory.createModifier(ts.SyntaxKind.PrivateKeyword)]); // private myMethod() {}
59+
*
60+
* @param modifiers the modifiers to apply to the method. Defaults to applying no modifiers if none are provided
61+
* @returns a new empty method
62+
*/
63+
const createMemberWithModifiers = (
64+
modifiers: ReadonlyArray<ts.ModifierLike> | undefined = undefined
65+
): ts.MethodDeclaration => {
66+
return ts.factory.createMethodDeclaration(
67+
modifiers,
68+
undefined,
69+
ts.factory.createIdentifier('myMethod'),
70+
undefined,
71+
undefined,
72+
[],
73+
undefined,
74+
ts.factory.createBlock([], false)
75+
);
76+
};
7277

78+
describe('isMemberPrivate', () => {
7379
it('returns false when the member has no modifiers', () => {
74-
const methodDeclaration = createMemberWithModifier();
80+
const methodDeclaration = createMemberWithModifiers();
7581

7682
expect(isMemberPrivate(methodDeclaration)).toBe(false);
7783
});
7884

7985
it('returns false when the member has a non-private modifier', () => {
80-
const methodDeclaration = createMemberWithModifier(ts.factory.createModifier(ts.SyntaxKind.PublicKeyword));
86+
const methodDeclaration = createMemberWithModifiers([ts.factory.createModifier(ts.SyntaxKind.PublicKeyword)]);
8187

8288
expect(isMemberPrivate(methodDeclaration)).toBe(false);
8389
});
@@ -86,9 +92,112 @@ describe('transform utils', () => {
8692
['private', ts.SyntaxKind.PrivateKeyword],
8793
['protected', ts.SyntaxKind.ProtectedKeyword],
8894
])('returns true when the member has a (%s) modifier', (_name, modifier) => {
89-
const methodDeclaration = createMemberWithModifier(ts.factory.createModifier(modifier));
95+
const methodDeclaration = createMemberWithModifiers([ts.factory.createModifier(modifier)]);
9096

9197
expect(isMemberPrivate(methodDeclaration)).toBe(true);
9298
});
9399
});
100+
101+
describe('retrieveModifierLike', () => {
102+
it("returns an empty array when no ModifierLike's are present", () => {
103+
const methodDeclaration = createMemberWithModifiers();
104+
105+
expect(retrieveModifierLike(methodDeclaration)).toEqual([]);
106+
});
107+
108+
it('returns all decorators and modifiers on a node', () => {
109+
const privateModifier = ts.factory.createModifier(ts.SyntaxKind.PrivateKeyword);
110+
const nonSenseDecorator = ts.factory.createDecorator(ts.factory.createStringLiteral('NonSenseDecorator'));
111+
112+
const methodDeclaration = createMemberWithModifiers([privateModifier, nonSenseDecorator]);
113+
const modifierLikes = retrieveModifierLike(methodDeclaration);
114+
115+
expect(modifierLikes).toHaveLength(2);
116+
expect(modifierLikes).toContain(privateModifier);
117+
expect(modifierLikes).toContain(nonSenseDecorator);
118+
});
119+
});
120+
121+
describe('retrieveTsDecorators', () => {
122+
it('returns undefined when a node cannot have decorators', () => {
123+
const node = ts.factory.createNumericLiteral(123);
124+
125+
const decorators = retrieveTsDecorators(node);
126+
127+
expect(decorators).toEqual(undefined);
128+
});
129+
130+
it('returns undefined when a node has undefined decorators', () => {
131+
// create a class declaration with name 'MyClass' and no decorators
132+
const node = ts.factory.createClassDeclaration(undefined, 'MyClass', undefined, undefined, []);
133+
134+
const decorators = retrieveTsDecorators(node);
135+
136+
expect(decorators).toEqual(undefined);
137+
});
138+
139+
it('returns undefined when a node has no decorators', () => {
140+
// create a class declaration with name 'MyClass' and no decorators
141+
const node = ts.factory.createClassDeclaration([], 'MyClass', undefined, undefined, []);
142+
143+
const decorators = retrieveTsDecorators(node);
144+
145+
expect(decorators).toEqual(undefined);
146+
});
147+
148+
it("returns a node's decorators", () => {
149+
const initialDecorators = [
150+
// no-op decorator, but it's good enough for testing purposes
151+
ts.factory.createDecorator(ts.factory.createStringLiteral('NonSenseDecorator')),
152+
];
153+
154+
// create a class declaration with name 'MyClass' and a decorator
155+
const node = ts.factory.createClassDeclaration(initialDecorators, 'MyClass', undefined, undefined, []);
156+
157+
const decorators = retrieveTsDecorators(node);
158+
159+
expect(decorators).toHaveLength(1);
160+
expect(decorators![0]).toEqual(initialDecorators[0]);
161+
});
162+
});
163+
164+
describe('retrieveTsModifiers', () => {
165+
it('returns undefined when a node cannot have modifiers', () => {
166+
const node = ts.factory.createNumericLiteral(123);
167+
168+
const modifiers = retrieveTsModifiers(node);
169+
170+
expect(modifiers).toEqual(undefined);
171+
});
172+
173+
it('returns undefined when a node has undefined modifiers', () => {
174+
// create a class declaration with name 'MyClass' and no modifiers
175+
const node = ts.factory.createClassDeclaration(undefined, 'MyClass', undefined, undefined, []);
176+
177+
const modifiers = retrieveTsModifiers(node);
178+
179+
expect(modifiers).toEqual(undefined);
180+
});
181+
182+
it('returns undefined when a node has no modifiers', () => {
183+
// create a class declaration with name 'MyClass' and no modifiers
184+
const node = ts.factory.createClassDeclaration([], 'MyClass', undefined, undefined, []);
185+
186+
const modifiers = retrieveTsModifiers(node);
187+
188+
expect(modifiers).toEqual(undefined);
189+
});
190+
191+
it("returns a node's modifiers", () => {
192+
const initialModifiers = [ts.factory.createModifier(ts.SyntaxKind.AbstractKeyword)];
193+
194+
// create a class declaration with name 'MyClass' and a 'abstract' modifier
195+
const node = ts.factory.createClassDeclaration(initialModifiers, 'MyClass', undefined, undefined, []);
196+
197+
const modifiers = retrieveTsModifiers(node);
198+
199+
expect(modifiers).toHaveLength(1);
200+
expect(modifiers![0]).toEqual(initialModifiers[0]);
201+
});
202+
});
94203
});

‎src/compiler/transformers/transform-utils.ts

+63-18
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,9 @@ export const getScriptTarget = () => {
1515
* @returns `true` if the member has the `private` or `protected` modifier attached to it. `false` otherwise
1616
*/
1717
export const isMemberPrivate = (member: ts.ClassElement): boolean => {
18-
if (
19-
member.modifiers &&
20-
member.modifiers.some((m) => m.kind === ts.SyntaxKind.PrivateKeyword || m.kind === ts.SyntaxKind.ProtectedKeyword)
21-
) {
22-
return true;
23-
}
24-
return false;
18+
return !!retrieveTsModifiers(member)?.some(
19+
(m) => m.kind === ts.SyntaxKind.PrivateKeyword || m.kind === ts.SyntaxKind.ProtectedKeyword
20+
);
2521
};
2622

2723
/**
@@ -161,10 +157,9 @@ const objectToObjectLiteral = (obj: { [key: string]: any }, refs: WeakSet<any>):
161157
*/
162158
export const createStaticGetter = (propName: string, returnExpression: ts.Expression): ts.GetAccessorDeclaration => {
163159
return ts.factory.createGetAccessorDeclaration(
164-
undefined,
165160
[ts.factory.createToken(ts.SyntaxKind.StaticKeyword)],
166161
propName,
167-
undefined,
162+
[],
168163
undefined,
169164
ts.factory.createBlock([ts.factory.createReturnStatement(returnExpression)])
170165
);
@@ -491,18 +486,20 @@ const getTypeReferenceLocation = (typeName: string, tsNode: ts.Node): d.Componen
491486

492487
// Loop through all top level exports to find if any reference to the type for 'local' reference location
493488
const isExported = sourceFileObj.statements.some((st) => {
489+
const statementModifiers = retrieveTsModifiers(st);
490+
494491
// Is the interface defined in the file and exported
495492
const isInterfaceDeclarationExported =
496493
ts.isInterfaceDeclaration(st) &&
497494
(<ts.Identifier>st.name).getText() === typeName &&
498-
Array.isArray(st.modifiers) &&
499-
st.modifiers.some((mod) => mod.kind === ts.SyntaxKind.ExportKeyword);
495+
Array.isArray(statementModifiers) &&
496+
statementModifiers.some((mod) => mod.kind === ts.SyntaxKind.ExportKeyword);
500497

501498
const isTypeAliasDeclarationExported =
502499
ts.isTypeAliasDeclaration(st) &&
503500
(<ts.Identifier>st.name).getText() === typeName &&
504-
Array.isArray(st.modifiers) &&
505-
st.modifiers.some((mod) => mod.kind === ts.SyntaxKind.ExportKeyword);
501+
Array.isArray(statementModifiers) &&
502+
statementModifiers.some((mod) => mod.kind === ts.SyntaxKind.ExportKeyword);
506503

507504
// Is the interface exported through a named export
508505
const isTypeInExportDeclaration =
@@ -616,11 +613,13 @@ export const getComponentTagName = (staticMembers: ts.ClassElement[]) => {
616613
return null;
617614
};
618615

619-
export const isStaticGetter = (member: ts.ClassElement) => {
616+
export const isStaticGetter = (member: ts.ClassElement): boolean => {
617+
const modifiers = retrieveTsModifiers(member);
620618
return (
621-
member.kind === ts.SyntaxKind.GetAccessor &&
622-
member.modifiers &&
623-
member.modifiers.some(({ kind }) => kind === ts.SyntaxKind.StaticKeyword)
619+
(member.kind === ts.SyntaxKind.GetAccessor &&
620+
Array.isArray(modifiers) &&
621+
modifiers.some(({ kind }) => kind === ts.SyntaxKind.StaticKeyword)) ??
622+
false
624623
);
625624
};
626625

@@ -712,7 +711,6 @@ export const createImportStatement = (importFnNames: string[], importPath: strin
712711
});
713712

714713
return ts.factory.createImportDeclaration(
715-
undefined,
716714
undefined,
717715
ts.factory.createImportClause(false, undefined, ts.factory.createNamedImports(importSpecifiers)),
718716
ts.factory.createStringLiteral(importPath)
@@ -761,3 +759,50 @@ export interface ConvertIdentifier {
761759
__identifier: boolean;
762760
__escapedText: string;
763761
}
762+
763+
/**
764+
* Helper method for retrieving all decorators & modifiers from a TypeScript {@link ts.Node} entity.
765+
*
766+
* Starting with TypeScript v4.8, decorators and modifiers have been coalesced into a single field, and retrieving
767+
* decorators directly has been deprecated. This helper function pulls all decorators & modifiers out of said field.
768+
*
769+
* @see {@link https://devblogs.microsoft.com/typescript/announcing-typescript-4-8/#decorators-are-placed-on-modifiers-on-typescripts-syntax-trees|The TypeScript 4.8 Announcement}
770+
*
771+
* @param node the node to pull decorators & modifiers out of
772+
* @returns a list containing decorators & modifiers on the node
773+
*/
774+
export const retrieveModifierLike = (node: ts.Node): ReadonlyArray<ts.ModifierLike> => {
775+
return [...(retrieveTsDecorators(node) ?? []), ...(retrieveTsModifiers(node) ?? [])];
776+
};
777+
778+
/**
779+
* Helper method for retrieving decorators from a TypeScript {@link ts.Node} entity.
780+
*
781+
* Starting with TypeScript v4.8, decorators and modifiers have been coalesced into a single field, and retrieving
782+
* decorators directly has been deprecated. This helper function is a utility that wraps various helper functions that
783+
* the TypeScript compiler exposes for pulling decorators out of said field.
784+
*
785+
* @see {@link https://devblogs.microsoft.com/typescript/announcing-typescript-4-8/#decorators-are-placed-on-modifiers-on-typescripts-syntax-trees|The TypeScript 4.8 Announcement}
786+
*
787+
* @param node the node to pull decorators out of
788+
* @returns a list containing 1+ decorators on the node, otherwise undefined
789+
*/
790+
export const retrieveTsDecorators = (node: ts.Node): ReadonlyArray<ts.Decorator> | undefined => {
791+
return ts.canHaveDecorators(node) ? ts.getDecorators(node) : undefined;
792+
};
793+
794+
/**
795+
* Helper method for retrieving modifiers from a TypeScript {@link ts.Node} entity.
796+
*
797+
* Starting with TypeScript v4.8, decorators and modifiers have been coalesced into a single field, and retrieving
798+
* modifiers directly has been deprecated. This helper function is a utility that wraps various helper functions that
799+
* the TypeScript compiler exposes for pulling modifiers out of said field.
800+
*
801+
* @see {@link https://devblogs.microsoft.com/typescript/announcing-typescript-4-8/#decorators-are-placed-on-modifiers-on-typescripts-syntax-trees|The TypeScript 4.8 Announcement}
802+
*
803+
* @param node the node to pull modifiers out of
804+
* @returns a list containing 1+ modifiers on the node, otherwise undefined
805+
*/
806+
export const retrieveTsModifiers = (node: ts.Node): ReadonlyArray<ts.Modifier> | undefined => {
807+
return ts.canHaveModifiers(node) ? ts.getModifiers(node) : undefined;
808+
};

‎src/compiler/transformers/update-component-class.ts

+5-6
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import ts from 'typescript';
22

33
import type * as d from '../../declarations';
4+
import { retrieveTsDecorators, retrieveTsModifiers } from './transform-utils';
45

56
export const updateComponentClass = (
67
transformOpts: d.TransformOptions,
78
classNode: ts.ClassDeclaration,
89
heritageClauses: ts.HeritageClause[] | ts.NodeArray<ts.HeritageClause>,
910
members: ts.ClassElement[]
10-
) => {
11-
let classModifiers = Array.isArray(classNode.modifiers) ? classNode.modifiers.slice() : [];
11+
): ts.ClassDeclaration | ts.VariableStatement => {
12+
let classModifiers = retrieveTsModifiers(classNode)?.slice() ?? [];
1213

1314
if (transformOpts.module === 'cjs') {
1415
// CommonJS, leave component class as is
@@ -21,8 +22,7 @@ export const updateComponentClass = (
2122
}
2223
return ts.factory.updateClassDeclaration(
2324
classNode,
24-
classNode.decorators,
25-
classModifiers,
25+
[...(retrieveTsDecorators(classNode) ?? []), ...classModifiers],
2626
classNode.name,
2727
classNode.typeParameters,
2828
heritageClauses,
@@ -42,7 +42,7 @@ const createConstClass = (
4242
) => {
4343
const className = classNode.name;
4444

45-
const classModifiers = (Array.isArray(classNode.modifiers) ? classNode.modifiers : []).filter((m) => {
45+
const classModifiers = (retrieveTsModifiers(classNode) ?? []).filter((m) => {
4646
// remove the export
4747
return m.kind !== ts.SyntaxKind.ExportKeyword;
4848
});
@@ -62,7 +62,6 @@ const createConstClass = (
6262
undefined,
6363
undefined,
6464
ts.factory.createClassExpression(
65-
undefined,
6665
classModifiers,
6766
undefined,
6867
classNode.typeParameters,

‎src/compiler/transformers/update-stencil-core-import.ts

-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ export const updateStencilCoreImports = (updatedCoreImportPath: string): ts.Tran
2929
const newImport = ts.factory.updateImportDeclaration(
3030
s,
3131
undefined,
32-
undefined,
3332
ts.factory.createImportClause(
3433
false,
3534
undefined,

‎src/testing/puppeteer/puppeteer-element.ts

+9-6
Original file line numberDiff line numberDiff line change
@@ -472,12 +472,15 @@ export class E2EElement extends MockHTMLElement implements pd.E2EElementInternal
472472
async e2eSync() {
473473
const executionContext = this._elmHandle.executionContext();
474474

475-
const { outerHTML, shadowRootHTML } = await executionContext.evaluate((elm: HTMLElement) => {
476-
return {
477-
outerHTML: elm.outerHTML,
478-
shadowRootHTML: elm.shadowRoot ? elm.shadowRoot.innerHTML : null,
479-
};
480-
}, this._elmHandle);
475+
const { outerHTML, shadowRootHTML } = await executionContext.evaluate<{ outerHTML: any; shadowRootHTML: any }>(
476+
(elm: HTMLElement) => {
477+
return {
478+
outerHTML: elm.outerHTML,
479+
shadowRootHTML: elm.shadowRoot ? elm.shadowRoot.innerHTML : null,
480+
};
481+
},
482+
this._elmHandle
483+
);
481484

482485
if (typeof shadowRootHTML === 'string') {
483486
(this as any).shadowRoot = parseHtmlToFragment(shadowRootHTML) as any;

0 commit comments

Comments
 (0)
Please sign in to comment.