Skip to content

Commit 5dd4f7f

Browse files
fix(compiler): don't allow shadowRoot getter to avoid hydration issues (#5912)
* fix(compiler): don't allow shadowRoot getter to avoid hydration issues * remove magic string * prettier
1 parent 5f8c969 commit 5dd4f7f

File tree

2 files changed

+92
-0
lines changed

2 files changed

+92
-0
lines changed

src/compiler/transformers/static-to-meta/component.ts

+48
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,14 @@ import { parseStringLiteral } from './string-literal';
2121
import { parseStaticStyles } from './styles';
2222
import { parseStaticWatchers } from './watchers';
2323

24+
const BLACKLISTED_COMPONENT_METHODS = [
25+
/**
26+
* If someone would define a getter called "shadowRoot" on a component
27+
* this would cause issues when Stencil tries to hydrate the component.
28+
*/
29+
'shadowRoot',
30+
];
31+
2432
/**
2533
* Given a {@see ts.ClassDeclaration} which represents a Stencil component
2634
* class declaration, parse and format various pieces of data about static class
@@ -148,6 +156,8 @@ export const parseStaticComponentMeta = (
148156
};
149157

150158
const visitComponentChildNode = (node: ts.Node) => {
159+
validateComponentMembers(node);
160+
151161
if (ts.isCallExpression(node)) {
152162
parseCallExpression(cmp, node);
153163
} else if (ts.isStringLiteral(node)) {
@@ -176,6 +186,44 @@ export const parseStaticComponentMeta = (
176186
return cmpNode;
177187
};
178188

189+
const validateComponentMembers = (node: ts.Node) => {
190+
/**
191+
* validate if:
192+
*/
193+
if (
194+
/**
195+
* the component has a getter called "shadowRoot"
196+
*/
197+
ts.isGetAccessorDeclaration(node) &&
198+
ts.isIdentifier(node.name) &&
199+
typeof node.name.escapedText === 'string' &&
200+
BLACKLISTED_COMPONENT_METHODS.includes(node.name.escapedText) &&
201+
/**
202+
* the parent node is a class declaration
203+
*/
204+
ts.isClassDeclaration(node.parent)
205+
) {
206+
const propName = node.name.escapedText;
207+
const decorator = ts.getDecorators(node.parent)[0];
208+
/**
209+
* the class is actually a Stencil component, has a decorator with a property named "tag"
210+
*/
211+
if (
212+
ts.isCallExpression(decorator.expression) &&
213+
decorator.expression.arguments.length === 1 &&
214+
ts.isObjectLiteralExpression(decorator.expression.arguments[0]) &&
215+
decorator.expression.arguments[0].properties.some(
216+
(prop) => ts.isPropertyAssignment(prop) && prop.name.getText() === 'tag',
217+
)
218+
) {
219+
const componentName = node.parent.name.getText();
220+
throw new Error(
221+
`The component "${componentName}" has a getter called "${propName}". This getter is reserved for use by Stencil components and should not be defined by the user.`,
222+
);
223+
}
224+
}
225+
};
226+
179227
const parseVirtualProps = (docs: d.CompilerJsDoc) => {
180228
return docs.tags
181229
.filter(({ name }) => name === 'virtualProp')

src/compiler/transformers/test/parse-component.spec.ts

+44
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,48 @@ describe('parse component', () => {
2323

2424
expect(t.componentClassName).toBe('CmpA');
2525
});
26+
27+
it('can not have shadowRoot getter', () => {
28+
let error: Error | undefined;
29+
try {
30+
transpileModule(`
31+
@Component({
32+
tag: 'cmp-a'
33+
})
34+
export class CmpA {
35+
get shadowRoot() {
36+
return this;
37+
}
38+
}
39+
`);
40+
} catch (err: unknown) {
41+
error = err as Error;
42+
}
43+
44+
expect(error.message).toContain(
45+
`The component "CmpA" has a getter called "shadowRoot". This getter is reserved for use by Stencil components and should not be defined by the user.`,
46+
);
47+
});
48+
49+
it('ignores shadowRoot getter in unrelated class', () => {
50+
const t = transpileModule(`
51+
@Component({
52+
tag: 'cmp-a'
53+
})
54+
export class CmpA {
55+
// use a better name for the getter
56+
get elementShadowRoot() {
57+
return this;
58+
}
59+
}
60+
61+
export class Unrelated {
62+
get shadowRoot() {
63+
return this;
64+
}
65+
}
66+
`);
67+
68+
expect(t.componentClassName).toBe('CmpA');
69+
});
2670
});

0 commit comments

Comments
 (0)