Skip to content

Commit b34bba0

Browse files
fix(compiler): don't allow shadowRoot getter to avoid hydration issues
1 parent 764a8ba commit b34bba0

File tree

2 files changed

+82
-0
lines changed

2 files changed

+82
-0
lines changed

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

+38
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,8 @@ export const parseStaticComponentMeta = (
148148
};
149149

150150
const visitComponentChildNode = (node: ts.Node) => {
151+
validateComponentMembers(node);
152+
151153
if (ts.isCallExpression(node)) {
152154
parseCallExpression(cmp, node);
153155
} else if (ts.isStringLiteral(node)) {
@@ -176,6 +178,42 @@ export const parseStaticComponentMeta = (
176178
return cmpNode;
177179
};
178180

181+
const validateComponentMembers = (node: ts.Node) => {
182+
/**
183+
* validate if:
184+
*/
185+
if (
186+
/**
187+
* the component has a getter called "shadowRoot"
188+
*/
189+
ts.isGetAccessorDeclaration(node) &&
190+
ts.isIdentifier(node.name) &&
191+
node.name.escapedText === 'shadowRoot' &&
192+
/**
193+
* the parent node is a class declaration
194+
*/
195+
ts.isClassDeclaration(node.parent)
196+
) {
197+
const decorator = ts.getDecorators(node.parent)[0];
198+
/**
199+
* the class is actually a Stencil component, has a decorator with a property named "tag"
200+
*/
201+
if (
202+
ts.isCallExpression(decorator.expression) &&
203+
decorator.expression.arguments.length === 1 &&
204+
ts.isObjectLiteralExpression(decorator.expression.arguments[0]) &&
205+
decorator.expression.arguments[0].properties.some(
206+
(prop) => ts.isPropertyAssignment(prop) && prop.name.getText() === 'tag',
207+
)
208+
) {
209+
const componentName = node.parent.name.getText();
210+
throw new Error(
211+
`The component "${componentName}" has a getter called "shadowRoot". This getter is reserved for use by Stencil components and should not be defined by the user.`,
212+
);
213+
}
214+
}
215+
};
216+
179217
const parseVirtualProps = (docs: d.CompilerJsDoc) => {
180218
return docs.tags
181219
.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)