@opensip-cli/lang-python 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.
Files changed (53) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +8 -0
  3. package/README.md +31 -0
  4. package/dist/__tests__/adapter.test.d.ts +2 -0
  5. package/dist/__tests__/adapter.test.d.ts.map +1 -0
  6. package/dist/__tests__/adapter.test.js +251 -0
  7. package/dist/__tests__/adapter.test.js.map +1 -0
  8. package/dist/__tests__/enclosing.test.d.ts +2 -0
  9. package/dist/__tests__/enclosing.test.d.ts.map +1 -0
  10. package/dist/__tests__/enclosing.test.js +42 -0
  11. package/dist/__tests__/enclosing.test.js.map +1 -0
  12. package/dist/__tests__/predicates.test.d.ts +2 -0
  13. package/dist/__tests__/predicates.test.d.ts.map +1 -0
  14. package/dist/__tests__/predicates.test.js +44 -0
  15. package/dist/__tests__/predicates.test.js.map +1 -0
  16. package/dist/__tests__/strip.test.d.ts +2 -0
  17. package/dist/__tests__/strip.test.d.ts.map +1 -0
  18. package/dist/__tests__/strip.test.js +77 -0
  19. package/dist/__tests__/strip.test.js.map +1 -0
  20. package/dist/__tests__/tree.test.d.ts +2 -0
  21. package/dist/__tests__/tree.test.d.ts.map +1 -0
  22. package/dist/__tests__/tree.test.js +36 -0
  23. package/dist/__tests__/tree.test.js.map +1 -0
  24. package/dist/adapter.d.ts +6 -0
  25. package/dist/adapter.d.ts.map +1 -0
  26. package/dist/adapter.js +13 -0
  27. package/dist/adapter.js.map +1 -0
  28. package/dist/enclosing.d.ts +18 -0
  29. package/dist/enclosing.d.ts.map +1 -0
  30. package/dist/enclosing.js +30 -0
  31. package/dist/enclosing.js.map +1 -0
  32. package/dist/index.d.ts +8 -0
  33. package/dist/index.d.ts.map +1 -0
  34. package/dist/index.js +13 -0
  35. package/dist/index.js.map +1 -0
  36. package/dist/parse.d.ts +18 -0
  37. package/dist/parse.d.ts.map +1 -0
  38. package/dist/parse.js +22 -0
  39. package/dist/parse.js.map +1 -0
  40. package/dist/predicates.d.ts +22 -0
  41. package/dist/predicates.d.ts.map +1 -0
  42. package/dist/predicates.js +22 -0
  43. package/dist/predicates.js.map +1 -0
  44. package/dist/shared-tree.d.ts +11 -0
  45. package/dist/shared-tree.d.ts.map +1 -0
  46. package/dist/shared-tree.js +15 -0
  47. package/dist/shared-tree.js.map +1 -0
  48. package/dist/strip.d.ts +5 -0
  49. package/dist/strip.d.ts.map +1 -0
  50. package/dist/strip.js +196 -0
  51. package/dist/strip.js.map +1 -0
  52. package/package.json +50 -0
  53. package/wasm/tree-sitter-python.wasm +0 -0
@@ -0,0 +1,77 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { stripStrings, stripComments } from '../strip.js';
3
+ describe('stripStrings', () => {
4
+ it('strips single-quoted string contents (replacing with whitespace)', () => {
5
+ const out = stripStrings(`x = 'hello'`);
6
+ expect(out).not.toContain('hello');
7
+ expect(out.startsWith(`x = '`)).toBe(true);
8
+ expect(out.endsWith(`'`)).toBe(true);
9
+ });
10
+ it('strips double-quoted strings', () => {
11
+ const out = stripStrings(`x = "hello"`);
12
+ expect(out).not.toContain('hello');
13
+ });
14
+ it('strips triple-single strings (multi-line)', () => {
15
+ const out = stripStrings("x = '''line1\nline2'''");
16
+ expect(out).not.toContain('line1');
17
+ expect(out).not.toContain('line2');
18
+ });
19
+ it('strips triple-double strings (multi-line)', () => {
20
+ const out = stripStrings('x = """line1\nline2"""');
21
+ expect(out).not.toContain('line1');
22
+ expect(out).not.toContain('line2');
23
+ });
24
+ it('handles escape sequences', () => {
25
+ const out = stripStrings(String.raw `x = 'a\'b'`);
26
+ expect(out).not.toContain('a');
27
+ });
28
+ it('handles raw strings (no escape processing)', () => {
29
+ const out = stripStrings(`x = r'abc'`);
30
+ expect(out).not.toContain('abc');
31
+ });
32
+ it('terminates non-triple string at newline (malformed input)', () => {
33
+ const out = stripStrings("x = 'unclosed\n");
34
+ expect(out).toContain('x = ');
35
+ });
36
+ it('passes through code with no strings unchanged', () => {
37
+ const code = 'def foo():\n return 1';
38
+ expect(stripStrings(code)).toBe(code);
39
+ });
40
+ it('handles triple string with escaped quotes', () => {
41
+ const out = stripStrings(String.raw `x = '''line1\'still in string'''`);
42
+ expect(out).not.toContain('line1');
43
+ });
44
+ it('handles unterminated triple string', () => {
45
+ const out = stripStrings(`x = '''unterminated`);
46
+ expect(out).toContain('x = ');
47
+ expect(out).not.toContain('unterminated');
48
+ });
49
+ it('handles unterminated single-line string at EOF', () => {
50
+ const out = stripStrings(`x = "unterminated`);
51
+ expect(out).toContain('x = ');
52
+ expect(out).not.toContain('unterminated');
53
+ });
54
+ });
55
+ describe('stripComments', () => {
56
+ it('strips line comments', () => {
57
+ const out = stripComments('x = 1 # comment');
58
+ expect(out).not.toContain('comment');
59
+ expect(out).toContain('x = 1');
60
+ });
61
+ it('does not treat # inside a string as a comment marker', () => {
62
+ // The implementation may also blank string contents — what matters
63
+ // is that the structure ('x = "..."') survives without the # being
64
+ // misinterpreted as a comment delimiter that swallows the rest.
65
+ const out = stripComments('x = "# not a comment"\ny = 2');
66
+ expect(out).toContain('x = "');
67
+ expect(out).toContain('y = 2');
68
+ });
69
+ it('strips multiple comment lines', () => {
70
+ const src = '# top\nx = 1 # mid\ny = 2 # end';
71
+ const out = stripComments(src);
72
+ expect(out).not.toContain('top');
73
+ expect(out).not.toContain('mid');
74
+ expect(out).not.toContain('end');
75
+ });
76
+ });
77
+ //# sourceMappingURL=strip.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"strip.test.js","sourceRoot":"","sources":["../../src/__tests__/strip.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=tree.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tree.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/tree.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,36 @@
1
+ import { RunScope, runWithScopeSync } from '@opensip-cli/core';
2
+ import { initParseCache } from '@opensip-cli/core/languages/parse-cache.js';
3
+ import { describe, expect, it } from 'vitest';
4
+ import { parsePython } from '../parse.js';
5
+ import { getSharedTree } from '../shared-tree.js';
6
+ describe('parsePython', () => {
7
+ it('produces a real tree-sitter tree + source', () => {
8
+ const src = 'def f():\n return 1\n';
9
+ const tree = parsePython(src, 'f.py');
10
+ expect(tree).not.toBeNull();
11
+ expect(tree?.source).toBe(src);
12
+ expect(tree?.tree.rootNode.type).toBe('module');
13
+ });
14
+ it('returns a partial (non-null) tree with hasError for malformed source', () => {
15
+ const tree = parsePython('def (:\n', 'bad.py');
16
+ expect(tree).not.toBeNull();
17
+ expect(tree?.tree.rootNode.hasError).toBe(true);
18
+ });
19
+ });
20
+ describe('getSharedTree', () => {
21
+ it('returns a tree without an active cache (direct parse)', () => {
22
+ const tree = getSharedTree('x.py', 'x = 1\n');
23
+ expect(tree?.tree.rootNode.type).toBe('module');
24
+ });
25
+ describe('with an active parse cache', () => {
26
+ it('returns the same cached tree identity on repeat calls', () => {
27
+ runWithScopeSync(new RunScope(), () => {
28
+ initParseCache();
29
+ const a = getSharedTree('x.py', 'x = 1\n');
30
+ const b = getSharedTree('x.py', 'x = 1\n');
31
+ expect(a).toBe(b);
32
+ });
33
+ });
34
+ });
35
+ });
36
+ //# sourceMappingURL=tree.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tree.test.js","sourceRoot":"","sources":["../../src/__tests__/tree.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAC/D,OAAO,EAAE,cAAc,EAAE,MAAM,4CAA4C,CAAC;AAC5E,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAE9C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAElD,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;IAC3B,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;QACnD,MAAM,GAAG,GAAG,0BAA0B,CAAC;QACvC,MAAM,IAAI,GAAG,WAAW,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;QACtC,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC;QAC5B,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC/B,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sEAAsE,EAAE,GAAG,EAAE;QAC9E,MAAM,IAAI,GAAG,WAAW,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;QAC/C,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC;QAC5B,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,EAAE,CAAC,uDAAuD,EAAE,GAAG,EAAE;QAC/D,MAAM,IAAI,GAAG,aAAa,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;QAC9C,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,4BAA4B,EAAE,GAAG,EAAE;QAC1C,EAAE,CAAC,uDAAuD,EAAE,GAAG,EAAE;YAC/D,gBAAgB,CAAC,IAAI,QAAQ,EAAE,EAAE,GAAG,EAAE;gBACpC,cAAc,EAAE,CAAC;gBACjB,MAAM,CAAC,GAAG,aAAa,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;gBAC3C,MAAM,CAAC,GAAG,aAAa,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;gBAC3C,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YACpB,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,6 @@
1
+ import { type PythonTree } from './parse.js';
2
+ import type { LanguageAdapter } from '@opensip-cli/core';
3
+ export declare const pythonAdapter: LanguageAdapter<PythonTree>;
4
+ /** Plugin contract — exported as the lang plugin's `adapters` array. */
5
+ export declare const adapters: readonly [LanguageAdapter<import("@opensip-cli/tree-sitter").ParsedFile, unknown>];
6
+ //# sourceMappingURL=adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAG1D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEzD,eAAO,MAAM,aAAa,EAAE,eAAe,CAAC,UAAU,CAOrD,CAAC;AAEF,wEAAwE;AACxE,eAAO,MAAM,QAAQ,oFAA2B,CAAC"}
@@ -0,0 +1,13 @@
1
+ import { parsePython } from './parse.js';
2
+ import { stripComments, stripStrings } from './strip.js';
3
+ export const pythonAdapter = {
4
+ id: 'python',
5
+ fileExtensions: ['.py', '.pyi'],
6
+ aliases: ['py'],
7
+ parse: parsePython,
8
+ stripStrings,
9
+ stripComments,
10
+ };
11
+ /** Plugin contract — exported as the lang plugin's `adapters` array. */
12
+ export const adapters = [pythonAdapter];
13
+ //# sourceMappingURL=adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.js","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAmB,MAAM,YAAY,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAIzD,MAAM,CAAC,MAAM,aAAa,GAAgC;IACxD,EAAE,EAAE,QAAQ;IACZ,cAAc,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC;IAC/B,OAAO,EAAE,CAAC,IAAI,CAAC;IACf,KAAK,EAAE,WAAW;IAClB,YAAY;IACZ,aAAa;CACd,CAAC;AAEF,wEAAwE;AACxE,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,aAAa,CAAU,CAAC"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Composed enclosing-scope helpers for Python (ADR-0010) — the per-language
3
+ * layer over the generic `findEnclosing`/`nameOf` from
4
+ * `@opensip-cli/tree-sitter`. Mirrors `lang-typescript`'s
5
+ * `findEnclosingFunction` / `getEnclosingFunctionName`.
6
+ */
7
+ import { type Node } from '@opensip-cli/tree-sitter';
8
+ /** The nearest enclosing `def` of `node`, or `null` at module scope. */
9
+ export declare function findEnclosingFunction(node: Node): Node | null;
10
+ /** The name of the nearest enclosing `def`, or `null`. */
11
+ export declare function getEnclosingFunctionName(node: Node): string | null;
12
+ /**
13
+ * True when `node` is a method — a `function_definition` whose *nearest*
14
+ * enclosing function-or-class is a class. A function nested inside another
15
+ * function is therefore not a method (its nearest enclosing scope is a `def`).
16
+ */
17
+ export declare function isMethod(node: Node): boolean;
18
+ //# sourceMappingURL=enclosing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"enclosing.d.ts","sourceRoot":"","sources":["../src/enclosing.ts"],"names":[],"mappings":"AACA;;;;;GAKG;AAEH,OAAO,EAAyB,KAAK,IAAI,EAAE,MAAM,0BAA0B,CAAC;AAI5E,wEAAwE;AACxE,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAE7D;AAED,0DAA0D;AAC1D,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,CAGlE;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAI5C"}
@@ -0,0 +1,30 @@
1
+ // @fitness-ignore-file duplicate-utility-functions -- ADR-0010: the per-language tree-sitter vocabulary intentionally shares helper names across lang-* with grammar-specific implementations; consolidating would defeat the substrate design.
2
+ /**
3
+ * Composed enclosing-scope helpers for Python (ADR-0010) — the per-language
4
+ * layer over the generic `findEnclosing`/`nameOf` from
5
+ * `@opensip-cli/tree-sitter`. Mirrors `lang-typescript`'s
6
+ * `findEnclosingFunction` / `getEnclosingFunctionName`.
7
+ */
8
+ import { findEnclosing, nameOf } from '@opensip-cli/tree-sitter';
9
+ import { isClass, isFunction } from './predicates.js';
10
+ /** The nearest enclosing `def` of `node`, or `null` at module scope. */
11
+ export function findEnclosingFunction(node) {
12
+ return findEnclosing(node, isFunction);
13
+ }
14
+ /** The name of the nearest enclosing `def`, or `null`. */
15
+ export function getEnclosingFunctionName(node) {
16
+ const fn = findEnclosingFunction(node);
17
+ return fn ? nameOf(fn) : null;
18
+ }
19
+ /**
20
+ * True when `node` is a method — a `function_definition` whose *nearest*
21
+ * enclosing function-or-class is a class. A function nested inside another
22
+ * function is therefore not a method (its nearest enclosing scope is a `def`).
23
+ */
24
+ export function isMethod(node) {
25
+ if (!isFunction(node))
26
+ return false;
27
+ const enclosing = findEnclosing(node, (n) => isFunction(n) || isClass(n));
28
+ return enclosing !== null && isClass(enclosing);
29
+ }
30
+ //# sourceMappingURL=enclosing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"enclosing.js","sourceRoot":"","sources":["../src/enclosing.ts"],"names":[],"mappings":"AAAA,gPAAgP;AAChP;;;;;GAKG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,EAAa,MAAM,0BAA0B,CAAC;AAE5E,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAEtD,wEAAwE;AACxE,MAAM,UAAU,qBAAqB,CAAC,IAAU;IAC9C,OAAO,aAAa,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;AACzC,CAAC;AAED,0DAA0D;AAC1D,MAAM,UAAU,wBAAwB,CAAC,IAAU;IACjD,MAAM,EAAE,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC;IACvC,OAAO,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAChC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,QAAQ,CAAC,IAAU;IACjC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IACpC,MAAM,SAAS,GAAG,aAAa,CAAC,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;IAC1E,OAAO,SAAS,KAAK,IAAI,IAAI,OAAO,CAAC,SAAS,CAAC,CAAC;AAClD,CAAC"}
@@ -0,0 +1,8 @@
1
+ export { pythonAdapter, adapters } from './adapter.js';
2
+ export { parsePython, type PythonTree } from './parse.js';
3
+ export { getSharedTree } from './shared-tree.js';
4
+ export { stripStrings, stripComments } from './strip.js';
5
+ export { isFunction, isClass, isComment, isString, isExcept, isConditional, isLoop, } from './predicates.js';
6
+ export { findEnclosingFunction, getEnclosingFunctionName, isMethod } from './enclosing.js';
7
+ export { childrenOf, findEnclosing, getColumn, getLineNumber, nameOf, namedChildrenOf, nodeText, walkNodes, type Node, } from '@opensip-cli/tree-sitter';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AACvD,OAAO,EAAE,WAAW,EAAE,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AACzD,OAAO,EACL,UAAU,EACV,OAAO,EACP,SAAS,EACT,QAAQ,EACR,QAAQ,EACR,aAAa,EACb,MAAM,GACP,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,qBAAqB,EAAE,wBAAwB,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAO3F,OAAO,EACL,UAAU,EACV,aAAa,EACb,SAAS,EACT,aAAa,EACb,MAAM,EACN,eAAe,EACf,QAAQ,EACR,SAAS,EACT,KAAK,IAAI,GACV,MAAM,0BAA0B,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,13 @@
1
+ export { pythonAdapter, adapters } from './adapter.js';
2
+ export { parsePython } from './parse.js';
3
+ export { getSharedTree } from './shared-tree.js';
4
+ export { stripStrings, stripComments } from './strip.js';
5
+ export { isFunction, isClass, isComment, isString, isExcept, isConditional, isLoop, } from './predicates.js';
6
+ export { findEnclosingFunction, getEnclosingFunctionName, isMethod } from './enclosing.js';
7
+ // Generic tree-sitter traversal/position vocabulary, re-exported so check
8
+ // packs reach the parser substrate THROUGH the language adapter (ADR-0039):
9
+ // the adapter owns the parser boundary; check packs depend on lang-python +
10
+ // fitness only, never on @opensip-cli/tree-sitter directly (enforced by
11
+ // dependency-cruiser).
12
+ export { childrenOf, findEnclosing, getColumn, getLineNumber, nameOf, namedChildrenOf, nodeText, walkNodes, } from '@opensip-cli/tree-sitter';
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AACvD,OAAO,EAAE,WAAW,EAAmB,MAAM,YAAY,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AACzD,OAAO,EACL,UAAU,EACV,OAAO,EACP,SAAS,EACT,QAAQ,EACR,QAAQ,EACR,aAAa,EACb,MAAM,GACP,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,qBAAqB,EAAE,wBAAwB,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE3F,0EAA0E;AAC1E,4EAA4E;AAC5E,4EAA4E;AAC5E,wEAAwE;AACxE,uBAAuB;AACvB,OAAO,EACL,UAAU,EACV,aAAa,EACb,SAAS,EACT,aAAa,EACb,MAAM,EACN,eAAe,EACf,QAAQ,EACR,SAAS,GAEV,MAAM,0BAA0B,CAAC"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @fileoverview Python parse — web-tree-sitter + vendored tree-sitter-python.wasm.
3
+ *
4
+ * ADR-0010: `lang-python` is the canonical Python parse substrate for the whole
5
+ * platform — fitness checks parse via the adapter (`getSharedTree`) and the
6
+ * graph Python adapter consumes this too. The grammar is loaded once at module
7
+ * top level (the WASM runtime is initialized by `@opensip-cli/tree-sitter`'s
8
+ * own top-level `Parser.init()`, statically imported here); a single reused
9
+ * parser keeps `parse()` synchronous and allocation-free. Tree-sitter recovers
10
+ * from syntax errors with MISSING nodes, so a malformed file yields a partial
11
+ * tree (non-null) rather than throwing — callers can inspect `rootNode.hasError`.
12
+ */
13
+ import { type ParsedFile } from '@opensip-cli/tree-sitter';
14
+ /** Parsed Python source: tree-sitter parse tree plus the original source text. */
15
+ export type PythonTree = ParsedFile;
16
+ /** Parses Python source into a {@link PythonTree}, or null when no tree is produced. */
17
+ export declare function parsePython(content: string, _filePath: string): PythonTree | null;
18
+ //# sourceMappingURL=parse.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../src/parse.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,OAAO,EAA0C,KAAK,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAOnG,kFAAkF;AAClF,MAAM,MAAM,UAAU,GAAG,UAAU,CAAC;AAEpC,wFAAwF;AACxF,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAGjF"}
package/dist/parse.js ADDED
@@ -0,0 +1,22 @@
1
+ /**
2
+ * @fileoverview Python parse — web-tree-sitter + vendored tree-sitter-python.wasm.
3
+ *
4
+ * ADR-0010: `lang-python` is the canonical Python parse substrate for the whole
5
+ * platform — fitness checks parse via the adapter (`getSharedTree`) and the
6
+ * graph Python adapter consumes this too. The grammar is loaded once at module
7
+ * top level (the WASM runtime is initialized by `@opensip-cli/tree-sitter`'s
8
+ * own top-level `Parser.init()`, statically imported here); a single reused
9
+ * parser keeps `parse()` synchronous and allocation-free. Tree-sitter recovers
10
+ * from syntax errors with MISSING nodes, so a malformed file yields a partial
11
+ * tree (non-null) rather than throwing — callers can inspect `rootNode.hasError`.
12
+ */
13
+ import { fileURLToPath } from 'node:url';
14
+ import { loadGrammar, createParser, parseToTree } from '@opensip-cli/tree-sitter';
15
+ const grammar = await loadGrammar(fileURLToPath(new URL('../wasm/tree-sitter-python.wasm', import.meta.url)));
16
+ const parser = createParser(grammar);
17
+ /** Parses Python source into a {@link PythonTree}, or null when no tree is produced. */
18
+ export function parsePython(content, _filePath) {
19
+ const tree = parseToTree(parser, content);
20
+ return tree ? { tree, source: content } : null;
21
+ }
22
+ //# sourceMappingURL=parse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse.js","sourceRoot":"","sources":["../src/parse.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAmB,MAAM,0BAA0B,CAAC;AAEnG,MAAM,OAAO,GAAG,MAAM,WAAW,CAC/B,aAAa,CAAC,IAAI,GAAG,CAAC,iCAAiC,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAC3E,CAAC;AACF,MAAM,MAAM,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;AAKrC,wFAAwF;AACxF,MAAM,UAAU,WAAW,CAAC,OAAe,EAAE,SAAiB;IAC5D,MAAM,IAAI,GAAG,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC1C,OAAO,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AACjD,CAAC"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * v1 per-language node-kind predicates for Python (ADR-0010). The generic
3
+ * traversal/position helpers live in `@opensip-cli/tree-sitter`; only the
4
+ * grammar-specific node `type` strings differ per language, and they live here.
5
+ * Node types are from the tree-sitter-python grammar.
6
+ */
7
+ import type { Node } from '@opensip-cli/tree-sitter';
8
+ /** A `def` — both top-level functions and methods are `function_definition`. */
9
+ export declare const isFunction: (node: Node) => boolean;
10
+ /** A `class` declaration. */
11
+ export declare const isClass: (node: Node) => boolean;
12
+ /** A `#` comment. */
13
+ export declare const isComment: (node: Node) => boolean;
14
+ /** A string literal (also covers f-strings / docstrings at the node level). */
15
+ export declare const isString: (node: Node) => boolean;
16
+ /** An `except[ … ]:` clause — Python's error-handling node. */
17
+ export declare const isExcept: (node: Node) => boolean;
18
+ /** An `if` statement. */
19
+ export declare const isConditional: (node: Node) => boolean;
20
+ /** A `for` or `while` loop. */
21
+ export declare const isLoop: (node: Node) => boolean;
22
+ //# sourceMappingURL=predicates.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"predicates.d.ts","sourceRoot":"","sources":["../src/predicates.ts"],"names":[],"mappings":"AACA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,0BAA0B,CAAC;AAErD,gFAAgF;AAChF,eAAO,MAAM,UAAU,GAAI,MAAM,IAAI,KAAG,OAA8C,CAAC;AAEvF,6BAA6B;AAC7B,eAAO,MAAM,OAAO,GAAI,MAAM,IAAI,KAAG,OAA2C,CAAC;AAEjF,qBAAqB;AACrB,eAAO,MAAM,SAAS,GAAI,MAAM,IAAI,KAAG,OAAkC,CAAC;AAE1E,+EAA+E;AAC/E,eAAO,MAAM,QAAQ,GAAI,MAAM,IAAI,KAAG,OAAiC,CAAC;AAExE,+DAA+D;AAC/D,eAAO,MAAM,QAAQ,GAAI,MAAM,IAAI,KAAG,OAAwC,CAAC;AAE/E,yBAAyB;AACzB,eAAO,MAAM,aAAa,GAAI,MAAM,IAAI,KAAG,OAAuC,CAAC;AAEnF,+BAA+B;AAC/B,eAAO,MAAM,MAAM,GAAI,MAAM,IAAI,KAAG,OAC8B,CAAC"}
@@ -0,0 +1,22 @@
1
+ // @fitness-ignore-file duplicate-utility-functions -- ADR-0010: the per-language tree-sitter vocabulary intentionally shares helper names across lang-* with grammar-specific implementations; consolidating would defeat the substrate design.
2
+ /**
3
+ * v1 per-language node-kind predicates for Python (ADR-0010). The generic
4
+ * traversal/position helpers live in `@opensip-cli/tree-sitter`; only the
5
+ * grammar-specific node `type` strings differ per language, and they live here.
6
+ * Node types are from the tree-sitter-python grammar.
7
+ */
8
+ /** A `def` — both top-level functions and methods are `function_definition`. */
9
+ export const isFunction = (node) => node.type === 'function_definition';
10
+ /** A `class` declaration. */
11
+ export const isClass = (node) => node.type === 'class_definition';
12
+ /** A `#` comment. */
13
+ export const isComment = (node) => node.type === 'comment';
14
+ /** A string literal (also covers f-strings / docstrings at the node level). */
15
+ export const isString = (node) => node.type === 'string';
16
+ /** An `except[ … ]:` clause — Python's error-handling node. */
17
+ export const isExcept = (node) => node.type === 'except_clause';
18
+ /** An `if` statement. */
19
+ export const isConditional = (node) => node.type === 'if_statement';
20
+ /** A `for` or `while` loop. */
21
+ export const isLoop = (node) => node.type === 'for_statement' || node.type === 'while_statement';
22
+ //# sourceMappingURL=predicates.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"predicates.js","sourceRoot":"","sources":["../src/predicates.ts"],"names":[],"mappings":"AAAA,gPAAgP;AAChP;;;;;GAKG;AAIH,gFAAgF;AAChF,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,IAAU,EAAW,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,qBAAqB,CAAC;AAEvF,6BAA6B;AAC7B,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,IAAU,EAAW,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,kBAAkB,CAAC;AAEjF,qBAAqB;AACrB,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,IAAU,EAAW,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,SAAS,CAAC;AAE1E,+EAA+E;AAC/E,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,IAAU,EAAW,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,QAAQ,CAAC;AAExE,+DAA+D;AAC/D,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,IAAU,EAAW,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,eAAe,CAAC;AAE/E,yBAAyB;AACzB,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,IAAU,EAAW,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,cAAc,CAAC;AAEnF,+BAA+B;AAC/B,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,IAAU,EAAW,EAAE,CAC5C,IAAI,CAAC,IAAI,KAAK,eAAe,IAAI,IAAI,CAAC,IAAI,KAAK,iBAAiB,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Cached Python parse entry point — the analog of `lang-typescript`'s
3
+ * `getSharedSourceFile`. Routes through `core`'s active `LanguageParseCache`
4
+ * (key `python:filePath:fingerprint`) so a file parsed by a fitness check and
5
+ * by the graph adapter in the same run is parsed once. Falls back to a direct
6
+ * `adapter.parse` when no cache is active (single-check mode).
7
+ */
8
+ import type { PythonTree } from './parse.js';
9
+ /** Returns the shared (cached) Python parse tree for `filePath`, or null when unparseable. */
10
+ export declare function getSharedTree(filePath: string, content: string): PythonTree | null;
11
+ //# sourceMappingURL=shared-tree.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared-tree.d.ts","sourceRoot":"","sources":["../src/shared-tree.ts"],"names":[],"mappings":"AACA;;;;;;GAMG;AAMH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,8FAA8F;AAC9F,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAElF"}
@@ -0,0 +1,15 @@
1
+ // @fitness-ignore-file duplicate-utility-functions -- ADR-0010: the per-language tree-sitter vocabulary intentionally shares helper names across lang-* with grammar-specific implementations; consolidating would defeat the substrate design.
2
+ /**
3
+ * Cached Python parse entry point — the analog of `lang-typescript`'s
4
+ * `getSharedSourceFile`. Routes through `core`'s active `LanguageParseCache`
5
+ * (key `python:filePath:fingerprint`) so a file parsed by a fitness check and
6
+ * by the graph adapter in the same run is parsed once. Falls back to a direct
7
+ * `adapter.parse` when no cache is active (single-check mode).
8
+ */
9
+ import { getParseTree } from '@opensip-cli/core/languages/parse-cache.js';
10
+ import { pythonAdapter } from './adapter.js';
11
+ /** Returns the shared (cached) Python parse tree for `filePath`, or null when unparseable. */
12
+ export function getSharedTree(filePath, content) {
13
+ return getParseTree(pythonAdapter, filePath, content);
14
+ }
15
+ //# sourceMappingURL=shared-tree.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared-tree.js","sourceRoot":"","sources":["../src/shared-tree.ts"],"names":[],"mappings":"AAAA,gPAAgP;AAChP;;;;;;GAMG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,4CAA4C,CAAC;AAE1E,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAI7C,8FAA8F;AAC9F,MAAM,UAAU,aAAa,CAAC,QAAgB,EAAE,OAAe;IAC7D,OAAO,YAAY,CAAC,aAAa,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;AACxD,CAAC"}
@@ -0,0 +1,5 @@
1
+ /** Replace string literal content with whitespace; preserves length. */
2
+ export declare const stripStrings: (content: string) => string;
3
+ /** Replace string literals AND comments with whitespace; preserves length. */
4
+ export declare const stripComments: (content: string) => string;
5
+ //# sourceMappingURL=strip.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"strip.d.ts","sourceRoot":"","sources":["../src/strip.ts"],"names":[],"mappings":"AAiNA,wEAAwE;AACxE,eAAO,MAAM,YAAY,6BAAwB,CAAC;AAClD,8EAA8E;AAC9E,eAAO,MAAM,aAAa,6BAAyB,CAAC"}
package/dist/strip.js ADDED
@@ -0,0 +1,196 @@
1
+ // Python string and comment stripping.
2
+ //
3
+ // Hand-written lexer that recognizes:
4
+ // - Line comments (# ... end-of-line)
5
+ // - Single-quoted strings ('...') and double-quoted strings ("...")
6
+ // - Triple-quoted strings ('''...''' and """...""") — multi-line
7
+ // - String prefixes (case-insensitive): r, b, u, f, rb, br, rf, fr
8
+ // e.g. r'raw', b"bytes", f"hello {x}", rb'raw-bytes'
9
+ // - Raw strings (prefix r/rb/br/rf/fr): backslash is an ordinary
10
+ // character EXCEPT before a quote (`\"` / `\'`), where it does NOT
11
+ // terminate the literal — matches CPython's tokenizer rule
12
+ // - F-string expression interpolation is intentionally NOT preserved —
13
+ // the entire body is treated as string content. This is a documented
14
+ // MVP limitation; checks that need to see f-string expressions should
15
+ // wait for tree-sitter integration.
16
+ //
17
+ // Both strip functions preserve byte length: replacement is whitespace
18
+ // (newlines preserved) so line/column positions remain stable.
19
+ //
20
+ // NOTE: this pack deliberately does NOT consume the C-family scanners
21
+ // from `@opensip-cli/core/languages/strip-utils.ts`
22
+ // (`scanRegularString`, `scanLineComment`, `scanBlockCommentNonNesting`,
23
+ // `scanCharLiteral`). Python's quote rules are the family outlier —
24
+ // strings open with either `'` or `"`, support eight ASCII prefix
25
+ // forms, and use `#` line comments — and the C-family helpers'
26
+ // signatures don't fit. If a second adopter (Ruby, Bash, Swift)
27
+ // appears, the right move is to lift a parameterized
28
+ // `scanQuotedString(quoteChar)` into core; with one consumer it stays
29
+ // here.
30
+ import { isIdentChar, makeStripper } from '@opensip-cli/core';
31
+ // Allowed Python string prefixes (lowercase). Case-insensitivity is
32
+ // handled at match time by lowercasing the candidate. Two-letter
33
+ // combinations come first so a longer prefix wins over a shorter one.
34
+ const TWO_CHAR_PREFIXES = new Set(['rb', 'br', 'rf', 'fr']);
35
+ const ONE_CHAR_PREFIXES = new Set(['r', 'b', 'u', 'f']);
36
+ function isAsciiLetter(ch) {
37
+ if (!ch)
38
+ return false;
39
+ const code = ch.codePointAt(0) ?? 0;
40
+ return (code >= 0x41 && code <= 0x5a) || (code >= 0x61 && code <= 0x7a);
41
+ }
42
+ /**
43
+ * If position `i` looks like the start of a Python string literal
44
+ * (optionally with prefix), return the index of the opening quote.
45
+ * Otherwise return null.
46
+ *
47
+ * The check is conservative: a prefix only counts if the character
48
+ * BEFORE it isn't an identifier character, so identifiers like
49
+ * `myvar = foo` or `bar` aren't mistaken for prefixes.
50
+ *
51
+ * Note: we deliberately do NOT distinguish raw from non-raw here.
52
+ * For *tokenization-bound* (which is all the strip pass needs), the
53
+ * two cases are identical: backslash always pairs with the next char.
54
+ * The raw/non-raw distinction only matters for value extraction —
55
+ * something the strip pass never does. See the scanner functions
56
+ * for the CPython-spec citation.
57
+ */
58
+ function matchStringStart(src, i) {
59
+ const c = src[i];
60
+ if (c === '"' || c === "'") {
61
+ return { quoteIndex: i };
62
+ }
63
+ if (!isAsciiLetter(c))
64
+ return null;
65
+ // Reject if the previous character is part of an identifier — then
66
+ // this is the middle/end of an identifier, not a string prefix.
67
+ if (i > 0 && isIdentChar(src[i - 1]))
68
+ return null;
69
+ // Try two-character prefix first.
70
+ const c1 = src[i];
71
+ const c2 = src[i + 1];
72
+ if (c1 && c2) {
73
+ const two = (c1 + c2).toLowerCase();
74
+ if (TWO_CHAR_PREFIXES.has(two)) {
75
+ const after = src[i + 2];
76
+ if (after === '"' || after === "'") {
77
+ return { quoteIndex: i + 2 };
78
+ }
79
+ }
80
+ }
81
+ // Single-character prefix.
82
+ const one = c1?.toLowerCase();
83
+ if (one && ONE_CHAR_PREFIXES.has(one)) {
84
+ const after = src[i + 1];
85
+ if (after === '"' || after === "'") {
86
+ return { quoteIndex: i + 1 };
87
+ }
88
+ }
89
+ return null;
90
+ }
91
+ function scanTripleString(src, contentStart, quote) {
92
+ const len = src.length;
93
+ let i = contentStart;
94
+ while (i < len) {
95
+ const ch = src[i];
96
+ if (ch === '\\') {
97
+ // Backslash always pairs with the following character for
98
+ // tokenization purposes, in BOTH non-raw and raw strings. In
99
+ // non-raw, this is escape-sequence handling. In raw, escape
100
+ // sequences are not interpreted, but per CPython:
101
+ // "Even in a raw literal, quotes can be escaped with a
102
+ // backslash, but the backslash remains in the result."
103
+ // So `r"\""` is the 2-char string `\"`, terminated by the
104
+ // third `"`. We must therefore skip past `\<anything>` in raw
105
+ // mode too, otherwise the next quote is mis-read as terminator.
106
+ // Newlines are preserved because we never replace them.
107
+ i += 2;
108
+ continue;
109
+ }
110
+ if (ch === quote && src[i + 1] === quote && src[i + 2] === quote) {
111
+ return { contentStart, contentEnd: i, next: i + 3 };
112
+ }
113
+ i++;
114
+ }
115
+ // Unterminated — record what we have.
116
+ return { contentStart, contentEnd: len, next: len };
117
+ }
118
+ function scanSingleString(src, contentStart, quote) {
119
+ const len = src.length;
120
+ let i = contentStart;
121
+ while (i < len) {
122
+ const ch = src[i];
123
+ // Newline terminates a non-triple string in Python (it's a syntax
124
+ // error to span lines without explicit continuation, but for
125
+ // strip purposes treat newline as a terminator to avoid eating
126
+ // the rest of the file on malformed input).
127
+ if (ch === '\n') {
128
+ return { contentStart, contentEnd: i, next: i };
129
+ }
130
+ if (ch === '\\') {
131
+ // Backslash always pairs with the following character for
132
+ // tokenization purposes, in BOTH non-raw and raw strings. In
133
+ // non-raw, this is escape-sequence handling (and `\\\n` is line
134
+ // continuation). In raw, escape sequences are not interpreted,
135
+ // but per CPython:
136
+ // "Even in a raw literal, quotes can be escaped with a
137
+ // backslash, but the backslash remains in the result."
138
+ // So `r"\""` is the 2-char string `\"`, terminated by the
139
+ // third `"`. We must therefore skip past `\<anything>` in raw
140
+ // mode too, otherwise the next quote is mis-read as terminator.
141
+ i += 2;
142
+ continue;
143
+ }
144
+ if (ch === quote) {
145
+ return { contentStart, contentEnd: i, next: i + 1 };
146
+ }
147
+ i++;
148
+ }
149
+ return { contentStart, contentEnd: len, next: len };
150
+ }
151
+ function scan(src) {
152
+ const stringRegions = [];
153
+ const commentRegions = [];
154
+ const len = src.length;
155
+ let i = 0;
156
+ while (i < len) {
157
+ const c = src[i];
158
+ // Line comment: # ... \n
159
+ if (c === '#') {
160
+ const start = i;
161
+ i++;
162
+ while (i < len && src[i] !== '\n')
163
+ i++;
164
+ commentRegions.push({ start, end: i });
165
+ continue;
166
+ }
167
+ // String literal (with optional prefix).
168
+ const stringStart = matchStringStart(src, i);
169
+ if (stringStart) {
170
+ const { quoteIndex } = stringStart;
171
+ const quote = src[quoteIndex];
172
+ // Triple-quoted?
173
+ if (src[quoteIndex + 1] === quote && src[quoteIndex + 2] === quote) {
174
+ const contentStart = quoteIndex + 3;
175
+ const result = scanTripleString(src, contentStart, quote);
176
+ stringRegions.push({ start: result.contentStart, end: result.contentEnd });
177
+ i = result.next;
178
+ }
179
+ else {
180
+ const contentStart = quoteIndex + 1;
181
+ const result = scanSingleString(src, contentStart, quote);
182
+ stringRegions.push({ start: result.contentStart, end: result.contentEnd });
183
+ i = result.next;
184
+ }
185
+ continue;
186
+ }
187
+ i++;
188
+ }
189
+ return { stringRegions, commentRegions };
190
+ }
191
+ const stripper = makeStripper(scan);
192
+ /** Replace string literal content with whitespace; preserves length. */
193
+ export const stripStrings = stripper.stripStrings;
194
+ /** Replace string literals AND comments with whitespace; preserves length. */
195
+ export const stripComments = stripper.stripComments;
196
+ //# sourceMappingURL=strip.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"strip.js","sourceRoot":"","sources":["../src/strip.ts"],"names":[],"mappings":"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"}