Skip to content
33 changes: 19 additions & 14 deletions lib/rules/template-no-obsolete-elements.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,16 @@ const OBSOLETE = [
'tt',
'xmp',
];

function hasBindingInScopeChain(scope, name) {
for (let s = scope; s; s = s.upper) {
if (s.set && s.set.has(name)) {
return true;
}
}
return false;
}

/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
Expand All @@ -50,26 +60,21 @@ module.exports = {
},
create(context) {
const obsolete = new Set(OBSOLETE);
const blockParamsInScope = [];
const sourceCode = context.sourceCode;

return {
GlimmerBlockStatement(node) {
const params = node.program?.blockParams || [];
blockParamsInScope.push(...params);
},
'GlimmerBlockStatement:exit'(node) {
const params = node.program?.blockParams || [];
for (let i = 0; i < params.length; i++) {
blockParamsInScope.pop();
}
},
GlimmerElementNode(node) {
if (blockParamsInScope.includes(node.tag)) {
if (!obsolete.has(node.tag)) {
return;
}
if (obsolete.has(node.tag)) {
context.report({ node, messageId: 'obsolete', data: { element: node.tag } });
// Use the parent's scope so that the element's own `as |x|` params
// (which attach a block scope to this node) don't shadow its own tag
// name. e.g. `<marquee as |marquee|>` must still flag the outer tag.
const scope = sourceCode.getScope(node.parent);
if (hasBindingInScopeChain(scope, node.tag)) {
return;
}
context.report({ node, messageId: 'obsolete', data: { element: node.tag } });
},
};
},
Expand Down
9 changes: 9 additions & 0 deletions tests/lib/rules/template-no-obsolete-elements.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,22 @@ ruleTester.run('template-no-obsolete-elements', rule, {
`<template>{{#let (component 'whatever-here') as |plaintext|}}
<plaintext />
{{/let}}</template>`,
// Element-level block params (<Comp as |...|>) are now tracked
'<template><Comp as |plaintext|><plaintext /></Comp></template>',
'<template><Outer as |marquee|><marquee /></Outer></template>',
],
invalid: [
{
code: '<template><marquee></marquee></template>',
output: null,
errors: [{ messageId: 'obsolete' }],
},
// Element's own block params must not shadow its own tag name.
{
code: '<template><marquee as |marquee|></marquee></template>',
output: null,
errors: [{ messageId: 'obsolete', data: { element: 'marquee' } }],
},
],
});

Expand Down
Loading