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 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
- // Lazy-initialize the parser so the native addon is only loaded when
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
- if (!_parser) {
13
- const Parser = _require("tree-sitter");
14
- const pythonModule = _require("tree-sitter-python");
15
- _language = pythonModule.language;
16
- _QueryClass = Parser.Query;
17
- const p = new Parser();
18
- p.setLanguage(pythonModule);
19
- _parser = p;
20
- }
21
- return {
22
- parser: _parser,
23
- language: _language,
24
- QueryClass: _QueryClass,
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
- const { parser } = getRuntime();
35
- return parser.parse(source);
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
- const { language, QueryClass } = getRuntime();
39
- const expanded = expandShorthands(selector);
40
- return runTreeSitterQuery(ast, expanded, source, filePath, language, QueryClass);
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
- const { language, QueryClass } = getRuntime();
44
- validateTreeSitterQuery(expandShorthands(selector), language, QueryClass);
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
- interface TSNode {
3
- startPosition: {
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, QueryClass) {
10
+ export function runTreeSitterQuery(ast, pattern, source, filePath, language) {
11
11
  assertValidPattern(pattern);
12
12
  const tree = ast;
13
- const q = new QueryClass(language, pattern);
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
- if (seen.has(node))
25
+ const key = `${node.startIndex}:${node.endIndex}`;
26
+ if (seen.has(key))
20
27
  continue;
21
- seen.add(node);
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, QueryClass) {
40
+ export function validateTreeSitterQuery(pattern, language) {
34
41
  try {
35
42
  assertValidPattern(pattern);
36
- new QueryClass(language, pattern);
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)}`);
@@ -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(`(?<!@)\\b(${keys.join("|")})\\b`, "g");
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",
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.21.1",
36
- "tree-sitter-python": "^0.21.0"
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.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.0"
48
+ "ast-search-js": "1.0.1"
49
49
  }
50
50
  }