Skip to content

Commit cf13cc1

Browse files
WIP
1 parent 50e5966 commit cf13cc1

File tree

2 files changed

+37
-14
lines changed

2 files changed

+37
-14
lines changed

utils/src/ast-grep/shebang.test.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@ describe('shebang', () => {
1818
assert.equal(shebang?.text(), '#!/usr/bin/env node');
1919
});
2020

21-
it('should take the last shebang line if multiple exist on top of the code', () => {
21+
it('should take the first shebang line if multiple exist on top of the code', () => {
2222
const code = dedent`
23-
#!/usr/bin/env node 1
24-
#!/usr/bin/env node 2
25-
console.log("Hello, world!");
26-
`;
23+
#!/usr/bin/env node 1
24+
#!/usr/bin/env node 2
25+
console.log("Hello, world!");
26+
`;
2727
const ast = astGrep.parse(astGrep.Lang.JavaScript, code);
2828

2929
const shebang = getShebang(ast);

utils/src/ast-grep/shebang.ts

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,47 @@ const REGEX_ESCAPE_PATTERN = /[.*+?^${}()|[\]\\]/g;
44

55
/**
66
* Get the shebang line from the root.
7+
* According to ECMAScript spec, shebangs (InputElementHashbangOrRegExp) are only
8+
* valid at the start of a Script or Module. We find hash_bang_lines that appear
9+
* at the beginning before any actual code. When multiple consecutive shebangs exist at the top,
10+
* we return the last one as it would be the effective shebang used.
711
* @param root The root node to search.
812
* @returns The shebang line if found, otherwise null.
913
*/
10-
export const getShebang = (root: SgRoot) =>
11-
root.root().find({
14+
export const getShebang = (root: SgRoot) => {
15+
const allShebangs = root.root().findAll({
1216
rule: {
1317
kind: 'hash_bang_line',
1418
regex: '\\bnode(\\.exe)?\\b',
15-
not: {
16-
// tree-sitter wraps hash-bang in Error node
17-
// when it's not in the top of program node
18-
inside: {
19-
kind: 'ERROR',
20-
},
21-
},
2219
},
2320
});
2421

22+
if (!allShebangs.length) return null;
23+
24+
// Check if first shebang is at line 0 (start of file)
25+
const firstShebang = allShebangs[0];
26+
if (firstShebang.range().start.line !== 0) {
27+
return null; // Shebang not at start of file
28+
}
29+
30+
// Collect all consecutive shebangs from the start
31+
const validShebangs = [firstShebang];
32+
for (let i = 1; i < allShebangs.length; i++) {
33+
const prevLine = allShebangs[i - 1].range().end.line;
34+
const currentLine = allShebangs[i].range().start.line;
35+
36+
// Check if this shebang is on the next consecutive line
37+
if (currentLine === prevLine || currentLine === prevLine + 1) {
38+
validShebangs.push(allShebangs[i]);
39+
} else {
40+
break; // Stop at first non-consecutive shebang
41+
}
42+
}
43+
44+
// Return the last consecutive shebang from the start
45+
return validShebangs[validShebangs.length - 1];
46+
};
47+
2548
/**
2649
* Replace Node.js arguments in the shebang line.
2750
* @param root The root node to search.

0 commit comments

Comments
 (0)