forked from ember-cli/eslint-plugin-ember
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathis-native-element.js
More file actions
90 lines (84 loc) · 3.65 KB
/
is-native-element.js
File metadata and controls
90 lines (84 loc) · 3.65 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
'use strict';
const htmlTags = require('html-tags');
const svgTags = require('svg-tags');
const { mathmlTagNames } = require('mathml-tag-names');
// Authoritative set of native element tag names. Mirrors the approach
// established by #2689 (template-no-block-params-for-html-elements), which
// the maintainer requires for component-vs-element discrimination in this
// plugin. Heuristic approaches (PascalCase detection, etc.) were explicitly
// rejected there because a lowercase tag CAN be a component in GJS/GTS when
// the name is bound in scope (e.g. `const div = MyComponent; <div />`).
const ELEMENT_TAGS = new Set([...htmlTags, ...svgTags, ...mathmlTagNames]);
/**
* Returns true if the Glimmer element node is a native HTML / SVG / MathML
* element — i.e. the tag name is in the authoritative list AND is not
* shadowed by an in-scope binding.
*
* "Native" here means **spec-registered tag name** (in the HTML, SVG, or
* MathML spec registries, reached via the `html-tags` / `svg-tags` /
* `mathml-tag-names` packages). It is NOT the same as:
*
* - "native accessibility" / "widget-ness" — an ARIA-tree-semantics
* question (for example, whether something maps to a widget role)
* - "native interactive content" / "focus behavior" — an HTML content-model
* question about which elements are considered interactive in the spec
* - "natively focusable" / sequential-focus — see HTML §6.6.3
*
* This util answers only: "is this tag a first-class built-in element of one
* of the three markup-language standards, rather than a component invocation
* or a shadowed local binding?" Callers should combine it with whatever
* accessibility, interactivity, or focusability checks they need for more
* specific questions.
*
* Returns false for:
* - components (PascalCase, dotted, @-prefixed, this.-prefixed, ::-namespaced —
* none of these tag names appear in the HTML/SVG/MathML lists)
* - custom elements (`<my-widget>`) — accepted false negative; the web-
* components namespace is open and can't be enumerated
* - scope-bound identifiers (`<div>` when `div` is a local `let` / `const` /
* import / block-param in the enclosing scope)
*
* @param {object} node - GlimmerElementNode
* @param {object} [sourceCode] - ESLint SourceCode, for scope lookup. When
* omitted, the scope check is skipped (the result is then list-based only —
* suitable for unit tests).
*/
function isNativeElement(node, sourceCode) {
if (!node || typeof node.tag !== 'string') {
return false;
}
if (!ELEMENT_TAGS.has(node.tag)) {
return false;
}
if (!sourceCode || !node.parent) {
return true;
}
const scope = sourceCode.getScope(node.parent);
const firstPart = node.parts && node.parts[0];
// Walk the full scope chain so a module-level `const div = MyComponent`
// also shadows the native `<div>` tag, not just block-level bindings.
if (firstPart && hasBindingInScopeChain(scope, firstPart.name)) {
return false;
}
return true;
}
function hasBindingInScopeChain(scope, name) {
let current = scope;
while (current) {
if (current.variables && current.variables.some((v) => v.name === name)) {
return true;
}
current = current.upper;
}
return false;
}
/**
* Inverse of {@link isNativeElement}. Returns true when the node should NOT
* be treated as a native HTML element — either because it's a component
* invocation (PascalCase, dotted, @-prefixed, this.-prefixed, custom element)
* OR a tag name that's shadowed by a scope binding.
*/
function isComponentInvocation(node, sourceCode) {
return !isNativeElement(node, sourceCode);
}
module.exports = { isNativeElement, isComponentInvocation, ELEMENT_TAGS };