ember-eslint-parser 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ember-eslint-parser",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "description": "",
5
5
  "keywords": [],
6
6
  "repository": {
@@ -23,7 +23,7 @@
23
23
  "@glimmer/syntax": "^0.95.0",
24
24
  "@typescript-eslint/tsconfig-utils": "^8.57.1",
25
25
  "content-tag": "^4.1.1",
26
- "ember-estree": "^0.4.2",
26
+ "ember-estree": "^0.6.2",
27
27
  "eslint-scope": "^9.1.2",
28
28
  "html-tags": "^5.1.0",
29
29
  "mathml-tag-names": "^4.0.0",
@@ -161,12 +161,14 @@ export function parseForESLint(code, options) {
161
161
  const useTS = isTypescript || useTypescript;
162
162
 
163
163
  // Both paths create scopeManager inside the parser callback so it's
164
- // available when toTree invokes visitors during splice — no second pass.
164
+ // available when toTree invokes the visitor factory — no second pass.
165
165
  let scopeManager = null;
166
+ const glimmerComments = [];
166
167
 
167
168
  try {
168
169
  const result = toTree(code, {
169
170
  filePath,
171
+ tokens: true,
170
172
  parser: useTS
171
173
  ? (placeholderJS) => {
172
174
  let parseCode = placeholderJS;
@@ -196,11 +198,44 @@ export function parseForESLint(code, options) {
196
198
  });
197
199
  return { ast: program, scopeManager };
198
200
  },
199
- visitors: buildGlimmerVisitors(() => scopeManager, useTS),
201
+ // Factory form: scopeManager is set by the parser callback before this
202
+ // runs. Returns null when unavailable so ember-estree skips the walk.
203
+ // The comments array is passed through so comment handlers share a
204
+ // single closure over it.
205
+ visitors: () => buildGlimmerVisitors(scopeManager, useTS, glimmerComments),
200
206
  });
201
207
 
202
208
  if (!result.scopeManager) result.scopeManager = scopeManager;
203
209
 
210
+ // Forward each placeholder's tsNode onto its GlimmerTemplate replacement,
211
+ // so typed rules calling `getTypeAtLocation(glimmerTemplate)` see the
212
+ // placeholder's type (string for expression templates, void for class-
213
+ // member static blocks) instead of the TS error intrinsic.
214
+ const esMap = result.services?.esTreeNodeToTSNodeMap;
215
+ if (esMap && result.templateInfos) {
216
+ for (const ti of result.templateInfos) {
217
+ const ts = esMap.get(ti.placeholder);
218
+ if (ts && !esMap.has(ti.ast)) esMap.set(ti.ast, ts);
219
+ }
220
+ }
221
+
222
+ // Surface Glimmer template comments in program.comments as type:'Block'
223
+ // so ESLint's inline-config scanner and plugin rules recognise them.
224
+ if (glimmerComments.length > 0) {
225
+ const programNode = result.ast?.program ?? result.ast;
226
+ if (programNode) {
227
+ programNode.comments = [
228
+ ...(programNode.comments || []),
229
+ ...glimmerComments.map((c) => ({
230
+ type: 'Block',
231
+ value: c.value,
232
+ range: c.range,
233
+ loc: c.loc,
234
+ })),
235
+ ].sort((a, b) => a.range[0] - b.range[0]);
236
+ }
237
+ }
238
+
204
239
  if (result.services?.program) {
205
240
  const programAllowJs = result.services.program.getCompilerOptions?.()?.allowJs;
206
241
  if (
@@ -231,7 +266,14 @@ export function parseForESLint(code, options) {
231
266
  .split(':')
232
267
  .slice(-2)
233
268
  .map((x) => parseInt(x));
234
- const err = new Error(e.source_code || e.message);
269
+ // e.source_code is the full diagnostic block. Strip ANSI escape codes
270
+ // (content-tag colours output in TTY environments, which breaks
271
+ // grep-based test:check assertions) and trim leading/trailing blank
272
+ // lines so consumers see a clean message.
273
+ // eslint-disable-next-line no-control-regex
274
+ const stripAnsi = (s) => s.replace(/\x1b\[[0-9;]*m/g, '');
275
+ const errorText = e.source_code ? stripAnsi(e.source_code).trim() : e.message;
276
+ const err = new Error(errorText);
235
277
  err.lineNumber = line;
236
278
  err.column = column;
237
279
  err.fileName = filePath;
@@ -27,7 +27,7 @@ export function parseForESLint(code, options) {
27
27
 
28
28
  let result;
29
29
  try {
30
- result = toTree(code, { templateOnly: true });
30
+ result = toTree(code, { templateOnly: true, tokens: true });
31
31
  } catch (e) {
32
32
  // Transform glimmer parse error to ESLint-compatible error
33
33
  const loc = e.location || (e.hash && e.hash.loc);
@@ -55,7 +55,14 @@ export function parseForESLint(code, options) {
55
55
  type: 'Program',
56
56
  body: [templateNode],
57
57
  tokens: templateNode.tokens,
58
- comments,
58
+ // Normalize Glimmer comment nodes to type:'Block' so ESLint's inline-config
59
+ // scanner and plugin rules that filter on type recognise them as comments.
60
+ comments: (comments || []).map((c) => ({
61
+ type: 'Block',
62
+ value: c.value,
63
+ range: c.range,
64
+ loc: c.loc,
65
+ })),
59
66
  range: [0, code.length],
60
67
  start: 0,
61
68
  end: code.length,
@@ -28,13 +28,10 @@ try {
28
28
  // ── Scope helpers ─────────────────────────────────────────────────────
29
29
 
30
30
  function findParentScope(scopeManager, nodePath) {
31
- let scope = null;
32
31
  let path = nodePath;
33
32
  while (path) {
34
- scope = scopeManager.acquire(path.node, true);
35
- if (scope) {
36
- return scope;
37
- }
33
+ const scope = scopeManager.acquire(path.node, true);
34
+ if (scope) return scope;
38
35
  path = path.parentPath;
39
36
  }
40
37
  return null;
@@ -151,26 +148,33 @@ function registerElementNode(node, path, scopeManager) {
151
148
 
152
149
  /**
153
150
  * Build Glimmer visitors for toTree that register scopes during traversal.
154
- * Uses a getter for scopeManager so it's available after the parser callback runs.
155
- * @param {function} getScopeManager - returns the scopeManager (may be null initially)
151
+ * Returns null when scopeManager is unavailable so the caller can skip the walk.
152
+ * When `collectComments` is provided, appends Glimmer comment nodes to it so
153
+ * callers can forward them into program.comments after the walk.
154
+ * @param {object|null} scopeManager
156
155
  * @param {boolean} isTypescript
157
- * @returns {object} visitors for toTree
156
+ * @param {Array|null} [collectComments] - optional array to collect Glimmer comment nodes into
157
+ * @returns {object|null} visitors for toTree, or null to skip
158
158
  */
159
- export function buildGlimmerVisitors(getScopeManager, isTypescript) {
160
- return {
159
+ export function buildGlimmerVisitors(scopeManager, isTypescript, collectComments) {
160
+ if (!scopeManager) return null;
161
+ const visitors = {
161
162
  GlimmerPathExpression(node, path) {
162
- const sm = getScopeManager();
163
- if (sm) registerPathExpression(node, path, sm);
163
+ registerPathExpression(node, path, scopeManager);
164
164
  },
165
165
  GlimmerElementNode(node, path) {
166
- const sm = getScopeManager();
167
- if (sm) registerElementNode(node, path, sm);
166
+ registerElementNode(node, path, scopeManager);
168
167
  },
169
168
  GlimmerBlockParams(node, path) {
170
- const sm = getScopeManager();
171
- if (sm) registerBlockParams(node, path, sm, isTypescript);
169
+ registerBlockParams(node, path, scopeManager, isTypescript);
172
170
  },
173
171
  };
172
+ if (collectComments) {
173
+ const push = (node) => collectComments.push(node);
174
+ visitors.GlimmerMustacheCommentStatement = push;
175
+ visitors.GlimmerCommentStatement = push;
176
+ }
177
+ return visitors;
174
178
  }
175
179
 
176
180
  // ── registerGlimmerScopes (fallback for JS/oxc path) ──────────────────