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.
- package/package.json +38 -12
- package/src/index.js +3 -15
- package/src/parse.js +111 -0
- package/src/print.js +913 -0
- package/src/transforms.js +230 -0
- package/examples/jscodeshift/node_modules/.bin/jscodeshift +0 -21
- package/examples/jscodeshift/node_modules/.bin/vitest +0 -21
- package/examples/jscodeshift/package.json +0 -18
- package/pnpm-workspace.yaml +0 -3
|
@@ -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
|
-
}
|
package/pnpm-workspace.yaml
DELETED