ast-search-python 1.0.3 → 1.0.5
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/README.md +2 -0
- package/build/index.d.ts +3 -3
- package/build/index.js +41 -27
- package/build/query.d.ts +2 -22
- package/build/query.js +13 -6
- package/build/shorthands.js +7 -4
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -19,6 +19,8 @@ npm install -g ast-search-js
|
|
|
19
19
|
npm install -g ast-search-python
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
+
> **pnpm users:** `tree-sitter` requires compiling a native addon. pnpm blocks build scripts by default, so the binary won't be built without extra steps. Either run `pnpm approve-builds -g` (select `tree-sitter` and `tree-sitter-python`), then reinstall — or use `npm install -g` instead.
|
|
23
|
+
|
|
22
24
|
## Usage
|
|
23
25
|
|
|
24
26
|
Pass `--plugin ast-search-python` to enable Python file support:
|
package/build/index.d.ts
CHANGED
|
@@ -3,9 +3,9 @@ export declare class PythonLanguageBackend implements LanguageBackend {
|
|
|
3
3
|
readonly langId = "python";
|
|
4
4
|
readonly name = "Python";
|
|
5
5
|
readonly extensions: Set<string>;
|
|
6
|
-
parse(source: string, _filePath: string): unknown
|
|
7
|
-
query(ast: unknown, selector: string, source: string, filePath: string): Match[]
|
|
8
|
-
validateSelector(selector: string): void
|
|
6
|
+
parse(source: string, _filePath: string): Promise<unknown>;
|
|
7
|
+
query(ast: unknown, selector: string, source: string, filePath: string): Promise<Match[]>;
|
|
8
|
+
validateSelector(selector: string): Promise<void>;
|
|
9
9
|
}
|
|
10
10
|
/**
|
|
11
11
|
* Register the Python backend with an ast-search LanguageRegistry.
|
package/build/index.js
CHANGED
|
@@ -1,28 +1,36 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
1
10
|
import { createRequire } from "module";
|
|
11
|
+
import path from "path";
|
|
2
12
|
import { expandShorthands } from "./shorthands.js";
|
|
3
13
|
import { runTreeSitterQuery, validateTreeSitterQuery } from "./query.js";
|
|
4
|
-
// tree-sitter is a CommonJS module; use createRequire for ESM compat.
|
|
5
14
|
const _require = createRequire(import.meta.url);
|
|
6
|
-
|
|
7
|
-
// a Python file is actually parsed (not at module import time).
|
|
8
|
-
let _parser;
|
|
9
|
-
let _language;
|
|
10
|
-
let _QueryClass;
|
|
15
|
+
let _runtimePromise = null;
|
|
11
16
|
function getRuntime() {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
17
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
18
|
+
if (_runtimePromise)
|
|
19
|
+
return _runtimePromise;
|
|
20
|
+
_runtimePromise = (() => __awaiter(this, void 0, void 0, function* () {
|
|
21
|
+
const { default: Parser } = yield import("web-tree-sitter");
|
|
22
|
+
const wasmDir = path.dirname(_require.resolve("web-tree-sitter"));
|
|
23
|
+
yield Parser.init({
|
|
24
|
+
locateFile: (_name) => path.join(wasmDir, "tree-sitter.wasm"),
|
|
25
|
+
});
|
|
26
|
+
const wasmPath = path.join(path.dirname(_require.resolve("tree-sitter-wasms/package.json")), "out", "tree-sitter-python.wasm");
|
|
27
|
+
const Python = yield Parser.Language.load(wasmPath);
|
|
28
|
+
const parser = new Parser();
|
|
29
|
+
parser.setLanguage(Python);
|
|
30
|
+
return { parser: parser, language: Python };
|
|
31
|
+
}))();
|
|
32
|
+
return _runtimePromise;
|
|
33
|
+
});
|
|
26
34
|
}
|
|
27
35
|
export class PythonLanguageBackend {
|
|
28
36
|
constructor() {
|
|
@@ -31,17 +39,23 @@ export class PythonLanguageBackend {
|
|
|
31
39
|
this.extensions = new Set([".py", ".pyw"]);
|
|
32
40
|
}
|
|
33
41
|
parse(source, _filePath) {
|
|
34
|
-
|
|
35
|
-
|
|
42
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
43
|
+
const { parser } = yield getRuntime();
|
|
44
|
+
return parser.parse(source);
|
|
45
|
+
});
|
|
36
46
|
}
|
|
37
47
|
query(ast, selector, source, filePath) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
48
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
49
|
+
const { language } = yield getRuntime();
|
|
50
|
+
const expanded = expandShorthands(selector);
|
|
51
|
+
return runTreeSitterQuery(ast, expanded, source, filePath, language);
|
|
52
|
+
});
|
|
41
53
|
}
|
|
42
54
|
validateSelector(selector) {
|
|
43
|
-
|
|
44
|
-
|
|
55
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
56
|
+
const { language } = yield getRuntime();
|
|
57
|
+
validateTreeSitterQuery(expandShorthands(selector), language);
|
|
58
|
+
});
|
|
45
59
|
}
|
|
46
60
|
}
|
|
47
61
|
/**
|
package/build/query.d.ts
CHANGED
|
@@ -1,23 +1,3 @@
|
|
|
1
1
|
import type { Match } from "ast-search-js/plugin";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
row: number;
|
|
5
|
-
column: number;
|
|
6
|
-
};
|
|
7
|
-
startIndex: number;
|
|
8
|
-
endIndex: number;
|
|
9
|
-
type: string;
|
|
10
|
-
}
|
|
11
|
-
interface TSCapture {
|
|
12
|
-
node: TSNode;
|
|
13
|
-
name: string;
|
|
14
|
-
}
|
|
15
|
-
interface TSQueryConstructor {
|
|
16
|
-
new (language: unknown, pattern: string): TSQuery;
|
|
17
|
-
}
|
|
18
|
-
interface TSQuery {
|
|
19
|
-
captures(node: TSNode): TSCapture[];
|
|
20
|
-
}
|
|
21
|
-
export declare function runTreeSitterQuery(ast: unknown, pattern: string, source: string, filePath: string, language: unknown, QueryClass: TSQueryConstructor): Match[];
|
|
22
|
-
export declare function validateTreeSitterQuery(pattern: string, language: unknown, QueryClass: TSQueryConstructor): void;
|
|
23
|
-
export {};
|
|
2
|
+
export declare function runTreeSitterQuery(ast: unknown, pattern: string, source: string, filePath: string, language: unknown): Match[];
|
|
3
|
+
export declare function validateTreeSitterQuery(pattern: string, language: unknown): void;
|
package/build/query.js
CHANGED
|
@@ -7,18 +7,25 @@ function assertValidPattern(pattern) {
|
|
|
7
7
|
`Use a shorthand (e.g. "fn", "call") or write a full S-expression (e.g. "(function_definition) @fn").`);
|
|
8
8
|
}
|
|
9
9
|
}
|
|
10
|
-
export function runTreeSitterQuery(ast, pattern, source, filePath, language
|
|
10
|
+
export function runTreeSitterQuery(ast, pattern, source, filePath, language) {
|
|
11
11
|
assertValidPattern(pattern);
|
|
12
12
|
const tree = ast;
|
|
13
|
-
|
|
13
|
+
// captures() only returns nodes that have a capture name (@something).
|
|
14
|
+
// If the user wrote a bare S-expression like (function_definition), add @_
|
|
15
|
+
// so results are returned.
|
|
16
|
+
const queryPattern = pattern.includes("@") ? pattern : `${pattern} @_`;
|
|
17
|
+
const q = language.query(queryPattern);
|
|
14
18
|
const captures = q.captures(tree.rootNode);
|
|
19
|
+
// web-tree-sitter may return different JS objects for the same node when
|
|
20
|
+
// multiple capture names match it, so deduplicate by position instead of identity.
|
|
15
21
|
const seen = new Set();
|
|
16
22
|
const results = [];
|
|
17
23
|
for (const capture of captures) {
|
|
18
24
|
const node = capture.node;
|
|
19
|
-
|
|
25
|
+
const key = `${node.startIndex}:${node.endIndex}`;
|
|
26
|
+
if (seen.has(key))
|
|
20
27
|
continue;
|
|
21
|
-
seen.add(
|
|
28
|
+
seen.add(key);
|
|
22
29
|
const text = source.slice(node.startIndex, node.endIndex);
|
|
23
30
|
const firstLine = text.split("\n")[0].trimEnd();
|
|
24
31
|
results.push({
|
|
@@ -30,10 +37,10 @@ export function runTreeSitterQuery(ast, pattern, source, filePath, language, Que
|
|
|
30
37
|
}
|
|
31
38
|
return results;
|
|
32
39
|
}
|
|
33
|
-
export function validateTreeSitterQuery(pattern, language
|
|
40
|
+
export function validateTreeSitterQuery(pattern, language) {
|
|
34
41
|
try {
|
|
35
42
|
assertValidPattern(pattern);
|
|
36
|
-
|
|
43
|
+
language.query(pattern);
|
|
37
44
|
}
|
|
38
45
|
catch (e) {
|
|
39
46
|
throw new Error(`Invalid tree-sitter query: ${e instanceof Error ? e.message : String(e)}`);
|
package/build/shorthands.js
CHANGED
|
@@ -41,11 +41,14 @@ export const PYTHON_SHORTHANDS = {
|
|
|
41
41
|
decorated: "(decorated_definition) @_",
|
|
42
42
|
};
|
|
43
43
|
export function expandShorthands(selector) {
|
|
44
|
-
// Replace bare shorthand words (not inside quotes and not preceded by @)
|
|
45
|
-
// with their S-expression. The negative lookbehind prevents expanding
|
|
46
|
-
// capture names like @fn into @(function_definition) @_
|
|
44
|
+
// Replace bare shorthand words (not inside quotes and not preceded by @ or ()
|
|
45
|
+
// with their S-expression. The negative lookbehind prevents expanding:
|
|
46
|
+
// - capture names like @fn into @(function_definition) @_
|
|
47
|
+
// - node type names inside S-expressions like (call ...) into ((call) @_ ...)
|
|
48
|
+
// which matters for shorthands whose name matches the tree-sitter node type
|
|
49
|
+
// exactly (call, await, yield, lambda, decorator).
|
|
47
50
|
const keys = Object.keys(PYTHON_SHORTHANDS);
|
|
48
|
-
const pattern = new RegExp(`(
|
|
51
|
+
const pattern = new RegExp(`(?<![@(])\\b(${keys.join("|")})\\b`, "g");
|
|
49
52
|
const parts = [];
|
|
50
53
|
let i = 0;
|
|
51
54
|
while (i < selector.length) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ast-search-python",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "Python language plugin for ast-search",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./build/index.js",
|
|
@@ -32,19 +32,19 @@
|
|
|
32
32
|
"provenance": true
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"tree-sitter": "^0.
|
|
36
|
-
"tree-sitter-
|
|
35
|
+
"web-tree-sitter": "^0.24.0",
|
|
36
|
+
"tree-sitter-wasms": "^0.1.12"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@jest/globals": "^29.7.0",
|
|
40
40
|
"@types/jest": "^29.5.12",
|
|
41
41
|
"@types/node": "^20.12.7",
|
|
42
|
-
"ast-search-js": "1.0.
|
|
42
|
+
"ast-search-js": "1.0.1",
|
|
43
43
|
"jest": "^29.7.0",
|
|
44
44
|
"ts-jest": "^29.1.2",
|
|
45
45
|
"typescript": "^5.4.5"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
|
-
"ast-search-js": "1.0.
|
|
48
|
+
"ast-search-js": "1.0.1"
|
|
49
49
|
}
|
|
50
50
|
}
|