ember-estree 0.0.0 → 0.1.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.
@@ -0,0 +1,230 @@
1
+ /**
2
+ * Glimmer AST → ESTree transform utilities.
3
+ *
4
+ * Ported from ember-eslint-parser's transforms.js, adapted for
5
+ * ember-estree's ESM architecture. Handles:
6
+ *
7
+ * - Type prefixing (all Glimmer types get a "Glimmer" prefix)
8
+ * - Range / loc fixing (converts template-local positions to file-level)
9
+ * - ElementNode `parts` and `name` fields
10
+ * - blockParams → virtual node creation
11
+ * - Empty hash nullification
12
+ * - Empty text node removal
13
+ */
14
+
15
+ import { traverse, visitorKeys as glimmerVisitorKeys } from "@glimmer/syntax";
16
+
17
+ /**
18
+ * Converts between character offsets and line/column positions.
19
+ * Lines are 1-based, columns are 0-based (matching ESTree & Glimmer conventions).
20
+ */
21
+ export class DocumentLines {
22
+ constructor(source) {
23
+ this.lineStarts = [0];
24
+ for (let i = 0; i < source.length; i++) {
25
+ if (source[i] === "\n") {
26
+ this.lineStarts.push(i + 1);
27
+ }
28
+ }
29
+ }
30
+
31
+ positionToOffset(pos) {
32
+ return this.lineStarts[pos.line - 1] + pos.column;
33
+ }
34
+
35
+ offsetToPosition(offset) {
36
+ let lo = 0;
37
+ let hi = this.lineStarts.length - 1;
38
+ while (lo < hi) {
39
+ const mid = (lo + hi + 1) >> 1;
40
+ if (this.lineStarts[mid] <= offset) lo = mid;
41
+ else hi = mid - 1;
42
+ }
43
+ return { line: lo + 1, column: offset - this.lineStarts[lo] };
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Traverse a Glimmer AST, set parent references, and categorize nodes.
49
+ */
50
+ function collectNodes(ast) {
51
+ const allNodes = [];
52
+ const comments = [];
53
+ const textNodes = [];
54
+ const emptyTextNodes = [];
55
+
56
+ traverse(ast, {
57
+ All(node, path) {
58
+ node.parent = path.parentNode;
59
+ allNodes.push(node);
60
+ if (node.type === "CommentStatement" || node.type === "MustacheCommentStatement") {
61
+ comments.push(node);
62
+ }
63
+ if (node.type === "TextNode") {
64
+ node.value = node.chars;
65
+ if (node.value.trim().length !== 0 || (node.parent && node.parent.type === "AttrNode")) {
66
+ textNodes.push(node);
67
+ } else {
68
+ emptyTextNodes.push(node);
69
+ }
70
+ }
71
+ },
72
+ });
73
+
74
+ return { allNodes, comments, textNodes, emptyTextNodes };
75
+ }
76
+
77
+ /**
78
+ * Remove nodes from their parent's children/body/parts arrays.
79
+ */
80
+ function removeFromParent(nodes) {
81
+ for (const node of nodes) {
82
+ const children =
83
+ (node.parent && (node.parent.children || node.parent.body || node.parent.parts)) || [];
84
+ const idx = children.indexOf(node);
85
+ if (idx >= 0) {
86
+ children.splice(idx, 1);
87
+ }
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Build the Glimmer visitor keys map with "Glimmer" prefix.
93
+ * Uses the visitor keys exported by @glimmer/syntax.
94
+ */
95
+ let _cachedGlimmerVisitorKeys = null;
96
+ export function buildGlimmerVisitorKeys() {
97
+ if (_cachedGlimmerVisitorKeys) return _cachedGlimmerVisitorKeys;
98
+ const keys = {};
99
+ for (const [k, v] of Object.entries(glimmerVisitorKeys)) {
100
+ keys[`Glimmer${k}`] = [...v];
101
+ }
102
+ if (!keys.GlimmerElementNode.includes("blockParamNodes")) {
103
+ keys.GlimmerElementNode.push("blockParamNodes", "parts");
104
+ }
105
+ keys.GlimmerProgram = ["body", "blockParamNodes"];
106
+ keys.GlimmerTemplate = ["body"];
107
+ _cachedGlimmerVisitorKeys = keys;
108
+ return keys;
109
+ }
110
+
111
+ /**
112
+ * Process a Glimmer AST into an ESTree-compatible form.
113
+ *
114
+ * @param {object} templateAST - The Glimmer AST (from ember-template-recast / @glimmer/syntax)
115
+ * @param {object} opts
116
+ * @param {number} opts.contentOffset - Byte offset where the template content begins in the full source
117
+ * @param {[number, number]} opts.templateRange - [start, end] byte range of the full <template>...</template> block
118
+ * @param {string} opts.source - The full source code
119
+ * @returns {object} The transformed AST
120
+ */
121
+ export function processGlimmerTemplate(templateAST, { contentOffset, templateRange, source }) {
122
+ // The Glimmer AST locs are relative to the inner template content only
123
+ const closingTagLen = "</template>".length;
124
+ const contentEnd = templateRange[1] - closingTagLen;
125
+ const contentStr = source.substring(contentOffset, contentEnd);
126
+ const contentDoc = new DocumentLines(contentStr);
127
+ const sourceDoc = new DocumentLines(source);
128
+
129
+ const toFileRange = (loc) => {
130
+ const locObj = loc.toJSON ? loc.toJSON() : loc;
131
+ return [
132
+ contentOffset + contentDoc.positionToOffset(locObj.start),
133
+ contentOffset + contentDoc.positionToOffset(locObj.end),
134
+ ];
135
+ };
136
+
137
+ const toFileLoc = (range) => ({
138
+ start: sourceDoc.offsetToPosition(range[0]),
139
+ end: sourceDoc.offsetToPosition(range[1]),
140
+ });
141
+
142
+ const { allNodes, comments, emptyTextNodes } = collectNodes(templateAST);
143
+
144
+ for (const n of allNodes) {
145
+ const loc = n.loc.toJSON ? n.loc.toJSON() : n.loc;
146
+
147
+ // Fix PathExpression head
148
+ if (n.type === "PathExpression") {
149
+ const head = n.head;
150
+ if (head && head.loc) {
151
+ const headLoc = head.loc.toJSON ? head.loc.toJSON() : head.loc;
152
+ if (headLoc && headLoc.start) {
153
+ head.range = toFileRange(headLoc);
154
+ head.start = head.range[0];
155
+ head.end = head.range[1];
156
+ head.loc = toFileLoc(head.range);
157
+ }
158
+ }
159
+ }
160
+
161
+ // Set range — Template root gets the full <template>...</template> range
162
+ n.range = n.type === "Template" ? [...templateRange] : toFileRange(loc);
163
+ n.start = n.range[0];
164
+ n.end = n.range[1];
165
+ n.loc = toFileLoc(n.range);
166
+
167
+ // Add parts and name to ElementNode
168
+ if (n.type === "ElementNode") {
169
+ n.name = n.tag;
170
+ // Compute the tag name range: starts 1 char after element start (<), length = tag.length
171
+ const tagStart = n.range[0] + 1; // skip "<"
172
+ const tagEnd = tagStart + n.tag.length;
173
+ const tagRange = [tagStart, tagEnd];
174
+ n.parts = [
175
+ {
176
+ original: n.tag,
177
+ name: n.tag,
178
+ type: "GlimmerElementNodePart",
179
+ range: tagRange,
180
+ start: tagRange[0],
181
+ end: tagRange[1],
182
+ loc: toFileLoc(tagRange),
183
+ },
184
+ ];
185
+ }
186
+
187
+ // Handle blockParams — create virtual nodes from the blockParams string array
188
+ if ("blockParams" in n && Array.isArray(n.blockParams)) {
189
+ n.blockParamNodes = n.blockParams.map((name) => {
190
+ return {
191
+ type: "GlimmerBlockParam",
192
+ name,
193
+ range: [...n.range],
194
+ start: n.range[0],
195
+ end: n.range[1],
196
+ loc: toFileLoc(n.range),
197
+ };
198
+ });
199
+ }
200
+
201
+ // Nullify empty hashes
202
+ if (
203
+ (n.type === "MustacheStatement" ||
204
+ n.type === "BlockStatement" ||
205
+ n.type === "SubExpression") &&
206
+ n.hash &&
207
+ n.hash.pairs &&
208
+ n.hash.pairs.length === 0
209
+ ) {
210
+ n.hash = null;
211
+ }
212
+
213
+ // Prefix type with "Glimmer"
214
+ n.type = `Glimmer${n.type}`;
215
+ }
216
+
217
+ // Clean up AST structure
218
+ removeFromParent(emptyTextNodes);
219
+ removeFromParent(comments);
220
+ for (const comment of comments) {
221
+ comment.type = "Block";
222
+ }
223
+
224
+ // Clear parent references (they cause circular JSON issues)
225
+ for (const n of allNodes) {
226
+ n.parent = null;
227
+ }
228
+
229
+ return templateAST;
230
+ }
@@ -1,21 +0,0 @@
1
- #!/bin/sh
2
- basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
-
4
- case `uname` in
5
- *CYGWIN*|*MINGW*|*MSYS*)
6
- if command -v cygpath > /dev/null 2>&1; then
7
- basedir=`cygpath -w "$basedir"`
8
- fi
9
- ;;
10
- esac
11
-
12
- if [ -z "$NODE_PATH" ]; then
13
- export NODE_PATH="/home/nvp/Development/NullVoxPopuli/ember-estree/node_modules/.pnpm/jscodeshift@17.3.0/node_modules/jscodeshift/bin/node_modules:/home/nvp/Development/NullVoxPopuli/ember-estree/node_modules/.pnpm/jscodeshift@17.3.0/node_modules/jscodeshift/node_modules:/home/nvp/Development/NullVoxPopuli/ember-estree/node_modules/.pnpm/jscodeshift@17.3.0/node_modules:/home/nvp/Development/NullVoxPopuli/ember-estree/node_modules/.pnpm/node_modules"
14
- else
15
- export NODE_PATH="/home/nvp/Development/NullVoxPopuli/ember-estree/node_modules/.pnpm/jscodeshift@17.3.0/node_modules/jscodeshift/bin/node_modules:/home/nvp/Development/NullVoxPopuli/ember-estree/node_modules/.pnpm/jscodeshift@17.3.0/node_modules/jscodeshift/node_modules:/home/nvp/Development/NullVoxPopuli/ember-estree/node_modules/.pnpm/jscodeshift@17.3.0/node_modules:/home/nvp/Development/NullVoxPopuli/ember-estree/node_modules/.pnpm/node_modules:$NODE_PATH"
16
- fi
17
- if [ -x "$basedir/node" ]; then
18
- exec "$basedir/node" "$basedir/../../../../node_modules/.pnpm/jscodeshift@17.3.0/node_modules/jscodeshift/bin/jscodeshift.js" "$@"
19
- else
20
- exec node "$basedir/../../../../node_modules/.pnpm/jscodeshift@17.3.0/node_modules/jscodeshift/bin/jscodeshift.js" "$@"
21
- fi
@@ -1,21 +0,0 @@
1
- #!/bin/sh
2
- basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
-
4
- case `uname` in
5
- *CYGWIN*|*MINGW*|*MSYS*)
6
- if command -v cygpath > /dev/null 2>&1; then
7
- basedir=`cygpath -w "$basedir"`
8
- fi
9
- ;;
10
- esac
11
-
12
- if [ -z "$NODE_PATH" ]; then
13
- export NODE_PATH="/home/nvp/Development/NullVoxPopuli/ember-estree/node_modules/.pnpm/vitest@3.2.4/node_modules/vitest/node_modules:/home/nvp/Development/NullVoxPopuli/ember-estree/node_modules/.pnpm/vitest@3.2.4/node_modules:/home/nvp/Development/NullVoxPopuli/ember-estree/node_modules/.pnpm/node_modules"
14
- else
15
- export NODE_PATH="/home/nvp/Development/NullVoxPopuli/ember-estree/node_modules/.pnpm/vitest@3.2.4/node_modules/vitest/node_modules:/home/nvp/Development/NullVoxPopuli/ember-estree/node_modules/.pnpm/vitest@3.2.4/node_modules:/home/nvp/Development/NullVoxPopuli/ember-estree/node_modules/.pnpm/node_modules:$NODE_PATH"
16
- fi
17
- if [ -x "$basedir/node" ]; then
18
- exec "$basedir/node" "$basedir/../../../../node_modules/.pnpm/vitest@3.2.4/node_modules/vitest/vitest.mjs" "$@"
19
- else
20
- exec node "$basedir/../../../../node_modules/.pnpm/vitest@3.2.4/node_modules/vitest/vitest.mjs" "$@"
21
- fi
@@ -1,18 +0,0 @@
1
- {
2
- "name": "jscodeshift",
3
- "version": "1.0.0",
4
- "description": "",
5
- "main": "index.js",
6
- "scripts": {
7
- "test": "echo \"Error: no test specified\" && exit 1"
8
- },
9
- "keywords": [],
10
- "author": "",
11
- "license": "ISC",
12
- "packageManager": "pnpm@10.18.2",
13
- "devDependencies": {
14
- "ember-estree": "workspace:*",
15
- "jscodeshift": "^17.3.0",
16
- "vitest": "^3.2.4"
17
- }
18
- }
@@ -1,3 +0,0 @@
1
- packages:
2
- - "."
3
- - "./examples/*"