Skip to content

Commit 7adfcda

Browse files
web-padawanclaude
andauthored
chore: mark readOnly properties in CEM analyzer config (#11181)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 0ab6f02 commit 7adfcda

File tree

1 file changed

+56
-0
lines changed

1 file changed

+56
-0
lines changed

custom-elements-manifest.config.js

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import { isStaticMember } from '@custom-elements-manifest/analyzer/src/utils/ast-helpers.js';
2+
import { extractMixinNodes } from '@custom-elements-manifest/analyzer/src/utils/mixins.js';
3+
14
const inheritanceDenyList = ['PolylitMixin', 'DirMixin'];
25

36
// Attribute types that can't be set via HTML attributes
@@ -131,12 +134,65 @@ function mixesPlugin() {
131134
};
132135
}
133136

137+
/**
138+
* CEM plugin that marks `readOnly: true` properties as `readonly` in custom-elements.json
139+
* to filter them out of Lit property bindings when generating web-types-lit.json files.
140+
*/
141+
function readonlyPlugin() {
142+
return {
143+
analyzePhase({ ts, node, moduleDoc }) {
144+
const mixinNodes = extractMixinNodes(node);
145+
const classNode = mixinNodes ? mixinNodes.mixinClass : ts.isClassDeclaration(node) ? node : undefined;
146+
if (!classNode) return;
147+
148+
// Find `static get properties()` method
149+
for (const member of classNode.members || []) {
150+
if (!ts.isGetAccessorDeclaration(member) || !isStaticMember(member) || member.name?.text !== 'properties') {
151+
continue;
152+
}
153+
154+
// Find the return statement's object literal
155+
const returnStmt = member.body?.statements?.find(ts.isReturnStatement);
156+
const returnObj = returnStmt?.expression;
157+
if (!returnObj || !ts.isObjectLiteralExpression(returnObj)) continue;
158+
159+
for (const prop of returnObj.properties) {
160+
if (!ts.isPropertyAssignment(prop)) continue;
161+
const propName = prop.name?.text;
162+
if (!propName) continue;
163+
164+
// Check if the property config object has `readOnly: true`
165+
const configObj = prop.initializer;
166+
if (!ts.isObjectLiteralExpression(configObj)) continue;
167+
168+
const hasReadOnly = configObj.properties.some(
169+
(p) =>
170+
ts.isPropertyAssignment(p) &&
171+
p.name?.text === 'readOnly' &&
172+
p.initializer?.kind === ts.SyntaxKind.TrueKeyword,
173+
);
174+
if (!hasReadOnly) continue;
175+
176+
// Find the matching member in moduleDoc declarations and mark it readonly
177+
for (const declaration of moduleDoc.declarations || []) {
178+
const cemMember = declaration.members?.find((m) => m.name === propName);
179+
if (cemMember) {
180+
cemMember.readonly = true;
181+
}
182+
}
183+
}
184+
}
185+
},
186+
};
187+
}
188+
134189
export default {
135190
globs: ['packages/**/src/(vaadin-*.js|*-mixin.js)'],
136191
packagejson: false,
137192
litelement: true,
138193
plugins: [
139194
mixesPlugin(),
195+
readonlyPlugin(),
140196
{
141197
packageLinkPhase({ customElementsManifest }) {
142198
for (const definition of customElementsManifest.modules) {

0 commit comments

Comments
 (0)