From 65ca6d442dd4826aa445e29d62da05c2b2ea92ce Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Tue, 17 Mar 2026 21:18:42 -0400 Subject: [PATCH 01/35] Extract to ember-estree --- package.json | 1 + pnpm-lock.yaml | 3 + src/parser/hbs-parser.js | 3 +- src/parser/transforms.js | 378 ++------------------------------------- src/utils/document.js | 102 ----------- 5 files changed, 16 insertions(+), 471 deletions(-) delete mode 100644 src/utils/document.js diff --git a/package.json b/package.json index 19d051c..7f42eb5 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "@glimmer/syntax": ">= 0.92.0", "@typescript-eslint/tsconfig-utils": "^8.38.0", "content-tag": "^4.1.0", + "ember-estree": "link:../ember-estree", "eslint-scope": "^9.1.1", "html-tags": "^5.1.0", "mathml-tag-names": "^4.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2819648..bd3aedf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,6 +23,9 @@ importers: content-tag: specifier: ^4.1.0 version: 4.1.0 + ember-estree: + specifier: link:../ember-estree + version: link:../ember-estree eslint-scope: specifier: ^9.1.1 version: 9.1.1 diff --git a/src/parser/hbs-parser.js b/src/parser/hbs-parser.js index 9722a74..8f38352 100644 --- a/src/parser/hbs-parser.js +++ b/src/parser/hbs-parser.js @@ -1,6 +1,5 @@ import * as eslintScope from 'eslint-scope'; -import DocumentLines from '../utils/document.js'; -import { processGlimmerTemplate, buildGlimmerVisitorKeys } from './transforms.js'; +import { processGlimmerTemplate, buildGlimmerVisitorKeys, DocumentLines } from 'ember-estree'; // Constant: Program + all Glimmer node types. Computed once at module load. const hbsVisitorKeys = { Program: ['body'], ...buildGlimmerVisitorKeys() }; diff --git a/src/parser/transforms.js b/src/parser/transforms.js index b2b3509..c5c0f7d 100644 --- a/src/parser/transforms.js +++ b/src/parser/transforms.js @@ -1,17 +1,19 @@ import { createRequire } from 'node:module'; import ContentTag from 'content-tag'; +import { isKeyword as glimmerIsKeyword } from '@glimmer/syntax'; import { - visitorKeys as glimmerVisitorKeys, - traverse as glimmerTraverse, - preprocess as glimmerPreprocess, - isKeyword as glimmerIsKeyword, -} from '@glimmer/syntax'; -import DocumentLines from '../utils/document.js'; + processGlimmerTemplate, + buildGlimmerVisitorKeys, + DocumentLines, + traverse, +} from 'ember-estree'; import { Reference, Scope, Variable, Definition } from 'eslint-scope'; import htmlTags from 'html-tags'; import svgTags from 'svg-tags'; import { mathmlTagNames } from 'mathml-tag-names'; +export { traverse, buildGlimmerVisitorKeys }; + const htmlTagsSet = new Set(htmlTags); const svgTagsSet = new Set(svgTags); const mathMLTagsSet = new Set(mathmlTagNames); @@ -104,367 +106,10 @@ function registerNodeInScope(node, scope, variable) { scope.references.push(ref); } -/** - * Builds the complete Glimmer visitor keys map with "Glimmer" prefix and - * additional keys needed for traversal (blockParamNodes, parts, etc). - * Result is cached since glimmerVisitorKeys is a constant. - * @return {object} - */ -let _cachedGlimmerVisitorKeys = null; -function buildGlimmerVisitorKeys() { - if (_cachedGlimmerVisitorKeys) return _cachedGlimmerVisitorKeys; - const keys = {}; - for (const [k, v] of Object.entries(glimmerVisitorKeys)) { - keys[`Glimmer${k}`] = [...v]; - } - if (!keys.GlimmerElementNode.includes('blockParamNodes')) { - keys.GlimmerElementNode.push('blockParamNodes', 'parts'); - } - keys.GlimmerProgram = ['body', 'blockParamNodes']; - keys.GlimmerTemplate = ['body']; - _cachedGlimmerVisitorKeys = keys; - return keys; -} - -/** - * traverses all nodes using the {visitorKeys} calling the callback function, visitor - * @param visitorKeys - * @param node - * @param visitor - */ -function traverse(visitorKeys, node, visitor) { - const allVisitorKeys = { ...visitorKeys, ...buildGlimmerVisitorKeys() }; - const queue = []; - - queue.push({ - node, - parent: null, - parentKey: null, - parentPath: null, - context: {}, - }); - - while (queue.length > 0) { - const currentPath = queue.pop(); - - visitor(currentPath); - - if (!currentPath.node) continue; - - const visitorKeys = allVisitorKeys[currentPath.node.type]; - if (!visitorKeys) { - continue; - } - - for (const visitorKey of visitorKeys) { - const child = currentPath.node[visitorKey]; - - if (!child) { - continue; - } else if (Array.isArray(child)) { - for (const item of child) { - queue.push({ - node: item, - parent: currentPath.node, - context: currentPath.context, - parentKey: visitorKey, - parentPath: currentPath, - }); - } - } else { - queue.push({ - node: child, - parent: currentPath.node, - context: currentPath.context, - parentKey: visitorKey, - parentPath: currentPath, - }); - } - } - } -} - function isUpperCase(char) { return char.toUpperCase() === char; } -function isAlphaNumeric(code) { - return !( - !(code > 47 && code < 58) && // numeric (0-9) - !(code > 64 && code < 91) && // upper alpha (A-Z) - !(code > 96 && code < 123) - ); -} - -function isWhiteSpaceCode(code) { - return ( - code === 32 /* space */ || - code === 9 /* tab */ || - code === 13 /* carriageReturn */ || - code === 10 /* lineFeed */ || - code === 11 /* verticalTab */ - ); -} - -/** - * simple tokenizer for templates, just splits it up into words and punctuators - * @param template {string} - * @param startOffset {number} - * @param doc {DocumentLines} - * @return {Token[]} - */ -function tokenize(template, doc, startOffset) { - const tokens = []; - let wordStart = -1; - function pushToken(value, type, range) { - const t = { - type, - value, - range, - start: range[0], - end: range[1], - loc: { - start: { ...doc.offsetToPosition(range[0]), index: range[0] }, - end: { ...doc.offsetToPosition(range[1]), index: range[1] }, - }, - }; - tokens.push(t); - } - for (let i = 0; i < template.length; i++) { - const code = template.charCodeAt(i); - if (isAlphaNumeric(code)) { - if (wordStart < 0) { - wordStart = i; - } - } else { - if (wordStart >= 0) { - pushToken(template.slice(wordStart, i), 'word', [startOffset + wordStart, startOffset + i]); - wordStart = -1; - } - if (!isWhiteSpaceCode(code)) { - pushToken(template[i], 'Punctuator', [startOffset + i, startOffset + i + 1]); - } - } - } - if (wordStart >= 0) { - pushToken(template.slice(wordStart), 'word', [ - startOffset + wordStart, - startOffset + template.length, - ]); - } - return tokens; -} - -/** - * Traverses a Glimmer AST, sets parent references, and categorizes nodes. - * @param {object} ast - * @return {{ allNodes: object[], comments: object[], textNodes: object[], emptyTextNodes: object[] }} - */ -function collectNodes(ast) { - const allNodes = []; - const comments = []; - const textNodes = []; - const emptyTextNodes = []; - - glimmerTraverse(ast, { - All(node, path) { - node.parent = path.parentNode; - allNodes.push(node); - if (node.type === 'CommentStatement' || node.type === 'MustacheCommentStatement') { - comments.push(node); - } - if (node.type === 'TextNode') { - node.value = node.chars; - if (node.value.trim().length !== 0 || (node.parent && node.parent.type === 'AttrNode')) { - textNodes.push(node); - } else { - emptyTextNodes.push(node); - } - } - }, - }); - - return { allNodes, comments, textNodes, emptyTextNodes }; -} - -/** - * Removes nodes from their parent's children/body/parts arrays. - * @param {object[]} nodes - */ -function removeFromParent(nodes) { - for (const node of nodes) { - const children = - (node.parent && (node.parent.children || node.parent.body || node.parent.parts)) || []; - const idx = children.indexOf(node); - if (idx >= 0) { - children.splice(idx, 1); - } - } -} - -/** - * Builds the final token stream by filtering out tokens covered by comments - * or text nodes, then merging text nodes back in sorted order. - * @param {object[]} rawTokens - * @param {object[]} comments - * @param {object[]} textNodes - * @return {object[]} - */ -function buildTokenStream(rawTokens, comments, textNodes) { - // Build sorted interval arrays for O(log n) exclusion checks - const commentIntervals = comments.map((c) => c.range).sort((a, b) => a[0] - b[0]); - const textNodeIntervals = textNodes.map((t) => t.range).sort((a, b) => a[0] - b[0]); - - /** - * Binary-search: is the token's range fully covered by any interval in `intervals`? - * Intervals must be sorted by start offset. - * @param {number[]} tokenRange - * @param {number[][]} intervals - */ - function isCovered(tokenRange, intervals) { - let lo = 0; - let hi = intervals.length - 1; - while (lo <= hi) { - const mid = (lo + hi) >> 1; - const iv = intervals[mid]; - if (iv[0] <= tokenRange[0] && iv[1] >= tokenRange[1]) { - return true; - } - if (iv[0] > tokenRange[0]) { - hi = mid - 1; - } else { - lo = mid + 1; - } - } - return false; - } - - // Single-pass filter: drop tokens covered by a comment or text node - const filteredTokens = rawTokens.filter( - (t) => !isCovered(t.range, commentIntervals) && !isCovered(t.range, textNodeIntervals) - ); - - // Merge text nodes (already sorted by position from the AST) into filteredTokens - // using a single linear merge pass instead of repeated splice calls. - const sortedTextNodes = [...textNodes].sort((a, b) => a.range[0] - b.range[0]); - const result = []; - let ti = 0; - for (const token of filteredTokens) { - while (ti < sortedTextNodes.length && sortedTextNodes[ti].range[0] < token.range[0]) { - result.push(sortedTextNodes[ti++]); - } - result.push(token); - } - while (ti < sortedTextNodes.length) { - result.push(sortedTextNodes[ti++]); - } - - return result; -} - -/** - * Parses a Glimmer template and produces a processed AST ready for ESLint. - * Shared between hbs-parser (standalone .hbs files) and gjs/gts parser (embedded templates). - * - * @param {object} options - * @param {string} options.templateContent - The template string to parse with glimmer - * @param {DocumentLines} options.codeLines - DocumentLines for the full source file - * @param {[number, number]} options.templateRange - Range [start, end] for the Template root node - * @param {string} [options.tokenSource] - String to tokenize (defaults to templateContent) - * @return {{ ast: object, comments: object[] }} - */ -function processGlimmerTemplate({ templateContent, codeLines, templateRange, tokenSource }) { - const offset = templateRange[0]; - const docLines = new DocumentLines(templateContent); - - /** Convert a Glimmer loc to a file-level [start, end] range */ - const toFileRange = (loc) => [ - offset + docLines.positionToOffset(loc.start), - offset + docLines.positionToOffset(loc.end), - ]; - /** Convert a file-level range to a file-level loc */ - const toFileLoc = (range) => ({ - start: codeLines.offsetToPosition(range[0]), - end: codeLines.offsetToPosition(range[1]), - }); - - const ast = glimmerPreprocess(templateContent, { mode: 'codemod' }); - const { allNodes, comments, textNodes, emptyTextNodes } = collectNodes(ast); - - // Fix ranges, locs, and prefix types with "Glimmer" - for (const n of allNodes) { - if (n.type === 'PathExpression') { - n.head.range = toFileRange(n.head.loc); - n.head.loc = toFileLoc(n.head.range); - } - - n.range = n.type === 'Template' ? [...templateRange] : toFileRange(n.loc); - n.start = n.range[0]; - n.end = n.range[1]; - n.loc = toFileLoc(n.range); - - if (n.type === 'ElementNode') { - n.name = n.tag; - n.parts = [n.path.head].map((p) => { - const range = toFileRange(p.loc); - return { - ...p, - name: p.original, - parent: n, - type: 'GlimmerElementNodePart', - range, - loc: toFileLoc(range), - }; - }); - } - - if ('blockParams' in n) { - n.params = (n.params || []).map((p) => { - const range = toFileRange(p.loc); - return { - ...p, - type: 'BlockParam', - name: p.original, - parent: n, - range, - loc: toFileLoc(range), - }; - }); - } - - // Nullify empty hashes before the type is renamed - if ( - (n.type === 'MustacheStatement' || - n.type === 'BlockStatement' || - n.type === 'SubExpression') && - n.hash && - n.hash.pairs && - n.hash.pairs.length === 0 - ) { - n.hash = null; - } - - n.type = `Glimmer${n.type}`; - } - - // Clean up AST structure - removeFromParent(emptyTextNodes); - removeFromParent(comments); - for (const comment of comments) { - comment.type = 'Block'; - } - - // Build final token stream - ast.tokens = buildTokenStream( - tokenize(tokenSource || templateContent, codeLines, offset), - comments, - textNodes - ); - ast.contents = templateContent; - - return { ast, comments }; -} - /** * Preprocesses the template info, parsing the template content to Glimmer AST, * fixing the offsets and locations of all nodes @@ -615,14 +260,15 @@ export function convertAst(result, preprocessedResult, visitorKeys) { result.scopeManager.declaredVariables || result.scopeManager.__declaredVariables; const vars = []; declaredVariables.set(node, vars); + const blockParamNodes = node.blockParamNodes || []; const virtualJSParentNode = { type: 'FunctionDeclaration', - params: node.params, + params: blockParamNodes, range: node.range, loc: node.loc, parent: path.parent, }; - for (const [i, b] of node.params.entries()) { + for (const [i, b] of blockParamNodes.entries()) { const v = new Variable(b.name, scope); v.identifiers.push(b); scope.variables.push(v); @@ -783,5 +429,3 @@ export function transformForLint(code, fileName) { output: jsCode, }; } - -export { traverse, tokenize, processGlimmerTemplate, buildGlimmerVisitorKeys }; diff --git a/src/utils/document.js b/src/utils/document.js deleted file mode 100644 index 3b2f1d3..0000000 --- a/src/utils/document.js +++ /dev/null @@ -1,102 +0,0 @@ -/** - * @typedef {{ line: number; column: number }} Position - */ - -// Helper class to convert line/column from and to offset -// taken and adapt from https://github.com/typed-ember/glint/blob/main/packages/core/src/language-server/util/position.ts -class DocumentLines { - /** - * @param {string} contents - */ - constructor(contents) { - this.lineStarts = computeLineStarts(contents); - } - - /** - * @param {Position} position - * @return {number} - */ - positionToOffset(position) { - const { line, column } = position; - return this.lineStarts[line - 1] + column; - } - - /** - * - * @param {number} position - * @return {Position} - */ - offsetToPosition(position) { - const lineStarts = this.lineStarts; - let lo = 0; - let hi = lineStarts.length - 1; - // Upper-biased midpoint is required here: we want the *rightmost* line whose - // start is ≤ position. With a lower-biased mid, when lo + 1 === hi and the - // condition is true, mid === lo and lo never advances, causing an infinite loop. - while (lo < hi) { - const mid = (lo + hi + 1) >> 1; - if (lineStarts[mid] <= position) { - lo = mid; - } else { - hi = mid - 1; - } - } - return { line: lo + 1, column: position - lineStarts[lo] }; - } -} - -/** - * @returns {number[]} - * @param {string} text - */ -function computeLineStarts(text) { - const result = []; - let pos = 0; - let lineStart = 0; - while (pos < text.length) { - const ch = text.charCodeAt(pos++); - if (ch === 13 /* carriageReturn */) { - if (text.charCodeAt(pos) === 10 /* lineFeed */) { - pos++; - } - result.push(lineStart); - lineStart = pos; - } else if ( - ch === 10 /* lineFeed */ || - ch === 8232 /* lineSeparator */ || - ch === 8233 /* paragraphSeparator */ - ) { - result.push(lineStart); - lineStart = pos; - } - } - result.push(lineStart); - return result; -} - -/* istanbul ignore next */ -/** - * @param {number} ch - * @return {boolean} - */ -function isLineBreak(ch) { - // ES5 7.3: - // The ECMAScript line terminator characters are listed in Table 3. - // Table 3: Line Terminator Characters - // Code Unit Value Name Formal Name - // \u000A Line Feed - // \u000D Carriage Return - // \u2028 Line separator - // \u2029 Paragraph separator - // Only the characters in Table 3 are treated as line terminators. Other new line or line - // breaking characters are treated as white space but not as line terminators. - return ( - ch === 10 /* lineFeed */ || - ch === 13 /* carriageReturn */ || - ch === 8232 /* lineSeparator */ || - ch === 8233 /* paragraphSeparator */ - ); -} - -export default DocumentLines; -export { isLineBreak }; From c1d13790aa9acda8216c58761e6abf821613d686 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Tue, 17 Mar 2026 21:19:48 -0400 Subject: [PATCH 02/35] Use git reference --- package.json | 2 +- pnpm-lock.yaml | 279 ++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 278 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 7f42eb5..84bfea0 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "@glimmer/syntax": ">= 0.92.0", "@typescript-eslint/tsconfig-utils": "^8.38.0", "content-tag": "^4.1.0", - "ember-estree": "link:../ember-estree", + "ember-estree": "github:NullVoxPopuli-ai-agent/ember-estree#bde42e3", "eslint-scope": "^9.1.1", "html-tags": "^5.1.0", "mathml-tag-names": "^4.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bd3aedf..93c6791 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -24,8 +24,8 @@ importers: specifier: ^4.1.0 version: 4.1.0 ember-estree: - specifier: link:../ember-estree - version: link:../ember-estree + specifier: github:NullVoxPopuli-ai-agent/ember-estree#bde42e3 + version: https://codeload.github.com/NullVoxPopuli-ai-agent/ember-estree/tar.gz/bde42e3 eslint-scope: specifier: ^9.1.1 version: 9.1.1 @@ -1034,6 +1034,15 @@ packages: resolution: {integrity: sha512-/SusdG+zgosc3t+9sPFVKSFOYyiSgLfXOT6lYNWoG1YtnhWDxlK4S8leZ0jhcVjemdaHln5rTyxCnq8oFLxqpQ==} engines: {node: 12.* || 14.* || >= 16} + '@emnapi/core@1.9.0': + resolution: {integrity: sha512-0DQ98G9ZQZOxfUcQn1waV2yS8aWdZ6kJMbYCJB3oUBecjWYO1fqJ+a1DRfPF3O5JEkwqwP1A9QEN/9mYm2Yd0w==} + + '@emnapi/runtime@1.9.0': + resolution: {integrity: sha512-QN75eB0IH2ywSpRpNddCRfQIhmJYBCJ1x5Lb3IscKAL8bMnVAKnRg8dCoXbHzVLLH7P38N2Z3mtulB7W0J0FKw==} + + '@emnapi/wasi-threads@1.2.0': + resolution: {integrity: sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==} + '@esbuild/aix-ppc64@0.21.5': resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} engines: {node: '>=12'} @@ -1418,6 +1427,9 @@ packages: resolution: {integrity: sha512-SkAyKAByB9l93Slyg8AUHGuM2kjvWioUTCckT/03J09jYnfEzMO/wSXmEhnKGYs6qx9De8TH4yJCl0Y9lRgnyQ==} engines: {node: '>=14.18.0'} + '@napi-rs/wasm-runtime@1.1.1': + resolution: {integrity: sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==} + '@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1': resolution: {integrity: sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==} @@ -1511,6 +1523,128 @@ packages: '@octokit/types@14.1.0': resolution: {integrity: sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==} + '@oxc-parser/binding-android-arm-eabi@0.119.0': + resolution: {integrity: sha512-e0ii/Tqwk5pAHZRY+ZyXOdKHNRNmE+dvTGQZ7xQ5XPH2Am59aktD30QvfcfwItGhNTLCj/5TYGH5RHvmvqaILQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxc-parser/binding-android-arm64@0.119.0': + resolution: {integrity: sha512-ha0xQpiStuoBv7HGazNKQWa6IRxri2+PpeojdAyBGnHGzfioA1GcStNGEGOyXvF+OxDfWvPuw5QiRYRUMtmgbQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxc-parser/binding-darwin-arm64@0.119.0': + resolution: {integrity: sha512-h/AIi5jfQz9WQUJJkkkHeXNYMhPtR72qnYZt0ZpM/LvlH/wpI5QkCPi7MWjjyY+m0JDorIXJyfOfccn8SbNSxQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxc-parser/binding-darwin-x64@0.119.0': + resolution: {integrity: sha512-15RwS/AawrgognvWsonI2eLKI5BqO0FzrpYXnzROysSR0x5RYsCc3UMFBwB1ph0UFFQzJy3ZbHHxfxp8RGr5Xg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@oxc-parser/binding-freebsd-x64@0.119.0': + resolution: {integrity: sha512-iKaayTIDqEj0yyNPL+0t/spNAxMv7O32uY4eu/ir8BvFNgavoRmN8uqxRj8sxQDle89N/1Iw0dgRjS3tiWrqlA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxc-parser/binding-linux-arm-gnueabihf@0.119.0': + resolution: {integrity: sha512-PDoOaOx8YWoxy19WNeMs6kOE0uFSb5EtA64Ye0wSp6sQpe+l8Gd+yjX2L+yNwd5MpDOvOy8KToa2bqCV4pf6iQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxc-parser/binding-linux-arm-musleabihf@0.119.0': + resolution: {integrity: sha512-AVxZ5Eo5squsUhpjnkCYuH20t5FCGV3HAP9UOKLxJQkmZW3kJvBGbfpH75ABxRrE2kGqmJW5FmS980u8v9Cepw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxc-parser/binding-linux-arm64-gnu@0.119.0': + resolution: {integrity: sha512-9vfdyT9gczSeSwsEkBHVjigI8SWo3iB9zxEzL+YMBUrN0ftCUkKQr27DaDZK4/cQ80t6KRB+g9sUmT2T2AGnOQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + + '@oxc-parser/binding-linux-arm64-musl@0.119.0': + resolution: {integrity: sha512-7BOq/tjSrtnp/ihw615uGcxMY3iya2qvVtwm15h2NvBZ6Jje+PC1GSUBOLfqGKJbUr9riSVV//a4iNhHI48Qdw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + + '@oxc-parser/binding-linux-ppc64-gnu@0.119.0': + resolution: {integrity: sha512-PQIrLJwoAaNyNSWBF+2SSgv44Jp+xpKVUA+8+PuoMhyBQ6lFSbQdaxewdn11i3heTFMYd2xF339HWax4S6MPVA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + + '@oxc-parser/binding-linux-riscv64-gnu@0.119.0': + resolution: {integrity: sha512-spNh4YhT9K+Ya5hr6NmI1MazKSKORD8u5/06hFbzTslLnmmxiGaLqJXhNKIYUH39ne/JD5rkoRkUcOB2/LpC3A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + + '@oxc-parser/binding-linux-riscv64-musl@0.119.0': + resolution: {integrity: sha512-hFoCTRxSJAcrNBYVlgNDDQq6LyJLYyhnJDlPtK/mWrPYS3x5/fUR9jc6wo5VyxKIL/0dDJBBWp19v81q9heU/A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + + '@oxc-parser/binding-linux-s390x-gnu@0.119.0': + resolution: {integrity: sha512-bl7jHJZq3W5tYEvKG3yWZTUKTNb0/BtyYSnfMIrQ7t8hajCH4i1g0q+14s0KmQQl1UHxIX/Gx/Ps6e92qJQJmQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + + '@oxc-parser/binding-linux-x64-gnu@0.119.0': + resolution: {integrity: sha512-m+DE7NhJIEGp4efSJnNfRf3swT25rbZ1FTIihV+pOLTI+k5yNguxvqT338mNu61OVSx0BfpV8QlO2nz43GR/Zg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + + '@oxc-parser/binding-linux-x64-musl@0.119.0': + resolution: {integrity: sha512-pHhnXZHUfd5pYzFLQfvx1DH2HY+L8DPZeh6SsQrsmoaODm1+j8VPeWLwGSrXQSz5f1kfT/mnzm1iNLVOGIeuaw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + + '@oxc-parser/binding-openharmony-arm64@0.119.0': + resolution: {integrity: sha512-8Fthv9nOec0hQLX16yjYyYIU+u8ZFuQojdQ3vNgXN+PcqI/bDohGgCATrxO69gLf0IzkyOmKmurXOQCYK8BYpQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxc-parser/binding-wasm32-wasi@0.119.0': + resolution: {integrity: sha512-KpOU6fLqevFDP6ndkgE4BPoceELM4bOsEsAXjpe+FKYuUyEzHssYPBmxouGpXDQeAeWTBIjosw5yElLMRPuccw==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@oxc-parser/binding-win32-arm64-msvc@0.119.0': + resolution: {integrity: sha512-omtTgAKIl6GQ40nG+wAWN8xMJLNtfmTdd0+wMIcrw1shX9y5TntAVIuiay3Du0wvUK9sgMpL07HYNphgHeZS0A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@oxc-parser/binding-win32-ia32-msvc@0.119.0': + resolution: {integrity: sha512-+0kqoCfv4WFP3e4BqcVEtf1moUuG9Zv5lo1aKcw1JakqJo008TGG+C2LnVM4QucGSZVQ/Ii/H5XCvrRbkeLQfA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxc-parser/binding-win32-x64-msvc@0.119.0': + resolution: {integrity: sha512-5kaKmBHD+OQjZzGAQQ9n8jWNvCRxu3MjElAjkCqsS3i2wiN3hqHlOPKwGDydYiB1gKdeYGlTjRYtuF4gBLDSxQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@oxc-project/types@0.119.0': + resolution: {integrity: sha512-9SCGhodOxEicD2kblitu34fGHcpmqgI3beYw/E22ehVLHzccHRFH91NmKt0MhZEaAwLpei6OOA9aB6Vuks9qAg==} + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -1652,6 +1786,9 @@ packages: '@tsconfig/ember@3.0.8': resolution: {integrity: sha512-OVnIsZIt/8q0VEtcdz3rRryNrm6gdJTxXlxefkGIrkZnME0wqslmwHlUEZ7mvh377df9FqBhNKrYNarhCW8zJA==} + '@tybys/wasm-util@0.10.1': + resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} + '@types/esrecurse@4.3.1': resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==} @@ -2649,6 +2786,10 @@ packages: resolution: {integrity: sha512-BtkjulweiXo9c3yVWrtexw2dTmBrvavD/xixNC6TKOBdrixUwU+6nuOO9dufDWsMxoid7MvtmDpzc9+mE8PdaA==} engines: {node: 10.* || >= 12.*} + ember-estree@https://codeload.github.com/NullVoxPopuli-ai-agent/ember-estree/tar.gz/bde42e3: + resolution: {tarball: https://codeload.github.com/NullVoxPopuli-ai-agent/ember-estree/tar.gz/bde42e3} + version: 0.3.0 + ember-rfc176-data@0.3.18: resolution: {integrity: sha512-JtuLoYGSjay1W3MQAxt3eINWXNYYQliK90tLwtb8aeCuQK8zKGCRbBodVIrkcTqshULMnRuTOS6t1P7oQk3g6Q==} @@ -4144,6 +4285,10 @@ packages: resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} engines: {node: '>= 0.4'} + oxc-parser@0.119.0: + resolution: {integrity: sha512-fNiKvO0ZHSUmINQlVY2It+vGbHxCvhpqJi0rZYFFOESoOy3fs5E4erKYGZtB/J1aULkjtY06aWNil4JxMsKXGg==} + engines: {node: ^20.19.0 || >=22.12.0} + p-finally@2.0.1: resolution: {integrity: sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw==} engines: {node: '>=8'} @@ -5376,6 +5521,9 @@ packages: resolution: {integrity: sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==} engines: {node: '>=18'} + zimmerframe@1.1.4: + resolution: {integrity: sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==} + snapshots: '@aashutoshrathi/word-wrap@1.2.6': {} @@ -6280,6 +6428,22 @@ snapshots: transitivePeerDependencies: - supports-color + '@emnapi/core@1.9.0': + dependencies: + '@emnapi/wasi-threads': 1.2.0 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.9.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.0': + dependencies: + tslib: 2.8.1 + optional: true + '@esbuild/aix-ppc64@0.21.5': optional: true @@ -6731,6 +6895,13 @@ snapshots: jju: 1.4.0 read-yaml-file: 1.1.0 + '@napi-rs/wasm-runtime@1.1.1': + dependencies: + '@emnapi/core': 1.9.0 + '@emnapi/runtime': 1.9.0 + '@tybys/wasm-util': 0.10.1 + optional: true + '@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1': dependencies: eslint-scope: 5.1.1 @@ -6850,6 +7021,70 @@ snapshots: dependencies: '@octokit/openapi-types': 25.1.0 + '@oxc-parser/binding-android-arm-eabi@0.119.0': + optional: true + + '@oxc-parser/binding-android-arm64@0.119.0': + optional: true + + '@oxc-parser/binding-darwin-arm64@0.119.0': + optional: true + + '@oxc-parser/binding-darwin-x64@0.119.0': + optional: true + + '@oxc-parser/binding-freebsd-x64@0.119.0': + optional: true + + '@oxc-parser/binding-linux-arm-gnueabihf@0.119.0': + optional: true + + '@oxc-parser/binding-linux-arm-musleabihf@0.119.0': + optional: true + + '@oxc-parser/binding-linux-arm64-gnu@0.119.0': + optional: true + + '@oxc-parser/binding-linux-arm64-musl@0.119.0': + optional: true + + '@oxc-parser/binding-linux-ppc64-gnu@0.119.0': + optional: true + + '@oxc-parser/binding-linux-riscv64-gnu@0.119.0': + optional: true + + '@oxc-parser/binding-linux-riscv64-musl@0.119.0': + optional: true + + '@oxc-parser/binding-linux-s390x-gnu@0.119.0': + optional: true + + '@oxc-parser/binding-linux-x64-gnu@0.119.0': + optional: true + + '@oxc-parser/binding-linux-x64-musl@0.119.0': + optional: true + + '@oxc-parser/binding-openharmony-arm64@0.119.0': + optional: true + + '@oxc-parser/binding-wasm32-wasi@0.119.0': + dependencies: + '@napi-rs/wasm-runtime': 1.1.1 + optional: true + + '@oxc-parser/binding-win32-arm64-msvc@0.119.0': + optional: true + + '@oxc-parser/binding-win32-ia32-msvc@0.119.0': + optional: true + + '@oxc-parser/binding-win32-x64-msvc@0.119.0': + optional: true + + '@oxc-project/types@0.119.0': {} + '@pkgjs/parseargs@0.11.0': optional: true @@ -6942,6 +7177,11 @@ snapshots: '@tsconfig/ember@3.0.8': {} + '@tybys/wasm-util@0.10.1': + dependencies: + tslib: 2.8.1 + optional: true + '@types/esrecurse@4.3.1': {} '@types/estree@1.0.6': {} @@ -8425,6 +8665,14 @@ snapshots: - '@babel/core' - supports-color + ember-estree@https://codeload.github.com/NullVoxPopuli-ai-agent/ember-estree/tar.gz/bde42e3: + dependencies: + '@glimmer/env': 0.1.7 + '@glimmer/syntax': 0.95.0 + content-tag: 4.1.0 + oxc-parser: 0.119.0 + zimmerframe: 1.1.4 + ember-rfc176-data@0.3.18: {} ember-router-generator@2.0.0: @@ -10485,6 +10733,31 @@ snapshots: object-keys: 1.1.1 safe-push-apply: 1.0.0 + oxc-parser@0.119.0: + dependencies: + '@oxc-project/types': 0.119.0 + optionalDependencies: + '@oxc-parser/binding-android-arm-eabi': 0.119.0 + '@oxc-parser/binding-android-arm64': 0.119.0 + '@oxc-parser/binding-darwin-arm64': 0.119.0 + '@oxc-parser/binding-darwin-x64': 0.119.0 + '@oxc-parser/binding-freebsd-x64': 0.119.0 + '@oxc-parser/binding-linux-arm-gnueabihf': 0.119.0 + '@oxc-parser/binding-linux-arm-musleabihf': 0.119.0 + '@oxc-parser/binding-linux-arm64-gnu': 0.119.0 + '@oxc-parser/binding-linux-arm64-musl': 0.119.0 + '@oxc-parser/binding-linux-ppc64-gnu': 0.119.0 + '@oxc-parser/binding-linux-riscv64-gnu': 0.119.0 + '@oxc-parser/binding-linux-riscv64-musl': 0.119.0 + '@oxc-parser/binding-linux-s390x-gnu': 0.119.0 + '@oxc-parser/binding-linux-x64-gnu': 0.119.0 + '@oxc-parser/binding-linux-x64-musl': 0.119.0 + '@oxc-parser/binding-openharmony-arm64': 0.119.0 + '@oxc-parser/binding-wasm32-wasi': 0.119.0 + '@oxc-parser/binding-win32-arm64-msvc': 0.119.0 + '@oxc-parser/binding-win32-ia32-msvc': 0.119.0 + '@oxc-parser/binding-win32-x64-msvc': 0.119.0 + p-finally@2.0.1: {} p-limit@1.3.0: @@ -11895,3 +12168,5 @@ snapshots: yocto-queue@1.1.1: {} yoctocolors@2.1.1: {} + + zimmerframe@1.1.4: {} From 8ebccdac4e2db6a8899938126d45377ff0f43077 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Tue, 17 Mar 2026 21:57:22 -0400 Subject: [PATCH 03/35] Use ember-estree minimal API, fix template positions and parent refs - Use toTree({ templateOnly: true }) instead of processGlimmerTemplate - Pass inner content (not full