@opensip-cli/lang-typescript 0.1.7 → 0.1.8

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
@@ -23,8 +23,8 @@ This package is published for the CLI and advanced plugin authors; most users sh
23
23
  ## Documentation
24
24
 
25
25
  - 📚 Project docs: https://opensip.ai/docs/opensip-cli/
26
- - 🧭 Package catalog (what every package does): https://github.com/opensip-ai/opensip-cli/blob/v0.1.7/docs/public/70-reference/02-package-catalog.md
27
- - 📦 Source: https://github.com/opensip-ai/opensip-cli/tree/v0.1.7/packages/languages/lang-typescript
26
+ - 🧭 Package catalog (what every package does): https://github.com/opensip-ai/opensip-cli/blob/v0.1.8/docs/public/70-reference/02-package-catalog.md
27
+ - 📦 Source: https://github.com/opensip-ai/opensip-cli/tree/v0.1.8/packages/languages/lang-typescript
28
28
 
29
29
  ## License
30
30
 
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=program-service.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"program-service.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/program-service.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,97 @@
1
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
2
+ import { tmpdir } from 'node:os';
3
+ import { dirname, join } from 'node:path';
4
+ import * as ts from 'typescript';
5
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
6
+ import { createTypeCheckedProgram, isTypeNullable } from '../program-service.js';
7
+ let dir;
8
+ beforeEach(() => {
9
+ dir = mkdtempSync(join(tmpdir(), 'opensip-progsvc-'));
10
+ });
11
+ afterEach(() => {
12
+ rmSync(dir, { recursive: true, force: true });
13
+ });
14
+ function write(rel, content) {
15
+ const abs = join(dir, rel);
16
+ mkdirSync(dirname(abs), { recursive: true });
17
+ writeFileSync(abs, content);
18
+ return abs;
19
+ }
20
+ /** Map of bare-identifier call expressions → their resolved result type. */
21
+ function callTypes(sf, checker) {
22
+ const out = new Map();
23
+ const visit = (n) => {
24
+ if (ts.isCallExpression(n) && ts.isIdentifier(n.expression)) {
25
+ out.set(n.expression.text, checker.getTypeAtLocation(n));
26
+ }
27
+ ts.forEachChild(n, visit);
28
+ };
29
+ visit(sf);
30
+ return out;
31
+ }
32
+ /** Resolve the type of a top-level `const <name>` initializer site by name. */
33
+ function varType(sf, checker, name) {
34
+ for (const stmt of sf.statements) {
35
+ if (!ts.isVariableStatement(stmt))
36
+ continue;
37
+ for (const decl of stmt.declarationList.declarations) {
38
+ if (ts.isIdentifier(decl.name) && decl.name.text === name) {
39
+ return checker.getTypeAtLocation(decl.name);
40
+ }
41
+ }
42
+ }
43
+ throw new Error(`variable ${name} not found`);
44
+ }
45
+ describe('createTypeCheckedProgram', () => {
46
+ it('builds a bound Program that resolves real (non-null vs nullable) return types', () => {
47
+ const file = write('src/sample.ts', [
48
+ 'export function getNullable(): string | null { return null; }',
49
+ 'export function getMaybe(): number | undefined { return undefined; }',
50
+ "export function getSafe(): string { return ''; }",
51
+ 'export function use() {',
52
+ ' return [getNullable(), getMaybe(), getSafe()];',
53
+ '}',
54
+ ].join('\n'));
55
+ const { checker, getSourceFile } = createTypeCheckedProgram([file], { projectRoot: dir });
56
+ const sf = getSourceFile(file);
57
+ expect(sf).toBeDefined();
58
+ const types = callTypes(sf, checker);
59
+ expect(isTypeNullable(types.get('getNullable'))).toBe(true);
60
+ expect(isTypeNullable(types.get('getMaybe'))).toBe(true);
61
+ expect(isTypeNullable(types.get('getSafe'))).toBe(false);
62
+ });
63
+ it('isTypeNullable fails open: `any`/`unknown` are not treated as nullable', () => {
64
+ const file = write('src/loose.ts', ['export const a: any = 1;', 'export const b: unknown = 1;'].join('\n'));
65
+ const { program, checker, getSourceFile } = createTypeCheckedProgram([file], {
66
+ projectRoot: dir,
67
+ });
68
+ expect(program.getSourceFiles().some((s) => s.fileName === file)).toBe(true);
69
+ const sf = getSourceFile(file);
70
+ expect(isTypeNullable(varType(sf, checker, 'a'))).toBe(false);
71
+ expect(isTypeNullable(varType(sf, checker, 'b'))).toBe(false);
72
+ });
73
+ it('honors an explicit tsconfig (strictNullChecks off → string|null reads non-nullable)', () => {
74
+ // A real project tsconfig with strict OFF: under it, `string | null` is the
75
+ // pre-strict `string` (null absorbed), so the service reflects the project's
76
+ // own type config rather than imposing strictness.
77
+ write('tsconfig.json', JSON.stringify({ compilerOptions: { strict: false } }));
78
+ const file = write('src/proj.ts', 'export const n: string | null = null;');
79
+ const built = createTypeCheckedProgram([file], {
80
+ projectRoot: dir,
81
+ tsconfigPath: 'tsconfig.json',
82
+ });
83
+ expect(built.tsconfigPath).toBeDefined();
84
+ const sf = built.getSourceFile(file);
85
+ expect(isTypeNullable(varType(sf, built.checker, 'n'))).toBe(false);
86
+ });
87
+ it('degrades gracefully when no tsconfig is found (fallback options, tsconfigPath undefined)', () => {
88
+ const file = write('src/orphan.ts', 'export const m: string | null = null;');
89
+ const built = createTypeCheckedProgram([file], { projectRoot: dir });
90
+ // The temp dir has no tsconfig and is outside any project tree.
91
+ expect(built.tsconfigPath).toBeUndefined();
92
+ const sf = built.getSourceFile(file);
93
+ // Fallback options enable strict, so `string | null` still reads as nullable.
94
+ expect(isTypeNullable(varType(sf, built.checker, 'm'))).toBe(true);
95
+ });
96
+ });
97
+ //# sourceMappingURL=program-service.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"program-service.test.js","sourceRoot":"","sources":["../../src/__tests__/program-service.test.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -7,6 +7,8 @@ export { stripStrings, stripComments } from './strip.js';
7
7
  export { filterContent } from './filter.js';
8
8
  export type { FilteredContent } from './filter.js';
9
9
  export { discoverTypescriptWorkspaceUnits } from './workspace-units.js';
10
+ export { createTypeCheckedProgram, isTypeNullable } from './program-service.js';
11
+ export type { TypeCheckedProgram, CreateTypeCheckedProgramOptions } from './program-service.js';
10
12
  export { findEnclosingFunction, findEnclosingFunctionBody, getEnclosingFunctionName, findEnclosingScope, isAsync, isInAsyncContext, isInsideConditionalBlock, } from './function-scope.js';
11
13
  export type { FunctionLikeNode } from './function-scope.js';
12
14
  export { getSharedSourceFile, walkNodes, getIdentifierName, getPropertyChain, getLineNumber, getColumn, isPropertyAccess, isLiteral, isInStringLiteral, findCallExpressions, findBinaryExpressions, findTemplateLiterals, isInComment, countUnescapedBackticks, } from './ast-utilities.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AAEjC,OAAO,EAAE,EAAE,EAAE,CAAC;AAEd,OAAO,EAAE,iBAAiB,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,gCAAgC,EAAE,MAAM,sBAAsB,CAAC;AAKxE,OAAO,EACL,qBAAqB,EACrB,yBAAyB,EACzB,wBAAwB,EACxB,kBAAkB,EAClB,OAAO,EACP,gBAAgB,EAChB,wBAAwB,GACzB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAM5D,OAAO,EACL,mBAAmB,EACnB,SAAS,EACT,iBAAiB,EACjB,gBAAgB,EAChB,aAAa,EACb,SAAS,EACT,gBAAgB,EAChB,SAAS,EACT,iBAAiB,EACjB,mBAAmB,EACnB,qBAAqB,EACrB,oBAAoB,EACpB,WAAW,EACX,uBAAuB,GACxB,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AAEjC,OAAO,EAAE,EAAE,EAAE,CAAC;AAEd,OAAO,EAAE,iBAAiB,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,gCAAgC,EAAE,MAAM,sBAAsB,CAAC;AAIxE,OAAO,EAAE,wBAAwB,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAChF,YAAY,EAAE,kBAAkB,EAAE,+BAA+B,EAAE,MAAM,sBAAsB,CAAC;AAKhG,OAAO,EACL,qBAAqB,EACrB,yBAAyB,EACzB,wBAAwB,EACxB,kBAAkB,EAClB,OAAO,EACP,gBAAgB,EAChB,wBAAwB,GACzB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAM5D,OAAO,EACL,mBAAmB,EACnB,SAAS,EACT,iBAAiB,EACjB,gBAAgB,EAChB,aAAa,EACb,SAAS,EACT,gBAAgB,EAChB,SAAS,EACT,iBAAiB,EACjB,mBAAmB,EACnB,qBAAqB,EACrB,oBAAoB,EACpB,WAAW,EACX,uBAAuB,GACxB,MAAM,oBAAoB,CAAC"}
package/dist/index.js CHANGED
@@ -12,6 +12,9 @@ export { typescriptQuery } from './query.js';
12
12
  export { stripStrings, stripComments } from './strip.js';
13
13
  export { filterContent } from './filter.js';
14
14
  export { discoverTypescriptWorkspaceUnits } from './workspace-units.js';
15
+ // Type-checked Program service (D2): the shared builder of a real ts.Program +
16
+ // bound TypeChecker for type-aware checks. See program-service.ts.
17
+ export { createTypeCheckedProgram, isTypeNullable } from './program-service.js';
15
18
  // Function-scope helpers — extracted from `ast-utilities.ts` into a
16
19
  // concern-named module. New scope helpers go in `./function-scope.ts`,
17
20
  // NOT in `ast-utilities.ts`.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAE7C,8EAA8E;AAC9E,0EAA0E;AAC1E,6EAA6E;AAC7E,oDAAoD;AACpD,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AACjC,qMAAqM;AACrM,OAAO,EAAE,EAAE,EAAE,CAAC;AAEd,OAAO,EAAE,iBAAiB,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,OAAO,EAAE,gCAAgC,EAAE,MAAM,sBAAsB,CAAC;AAExE,oEAAoE;AACpE,uEAAuE;AACvE,6BAA6B;AAC7B,OAAO,EACL,qBAAqB,EACrB,yBAAyB,EACzB,wBAAwB,EACxB,kBAAkB,EAClB,OAAO,EACP,gBAAgB,EAChB,wBAAwB,GACzB,MAAM,qBAAqB,CAAC;AAG7B,yEAAyE;AACzE,0EAA0E;AAC1E,4EAA4E;AAC5E,iDAAiD;AACjD,OAAO,EACL,mBAAmB,EACnB,SAAS,EACT,iBAAiB,EACjB,gBAAgB,EAChB,aAAa,EACb,SAAS,EACT,gBAAgB,EAChB,SAAS,EACT,iBAAiB,EACjB,mBAAmB,EACnB,qBAAqB,EACrB,oBAAoB,EACpB,WAAW,EACX,uBAAuB,GACxB,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAE7C,8EAA8E;AAC9E,0EAA0E;AAC1E,6EAA6E;AAC7E,oDAAoD;AACpD,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AACjC,qMAAqM;AACrM,OAAO,EAAE,EAAE,EAAE,CAAC;AAEd,OAAO,EAAE,iBAAiB,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,OAAO,EAAE,gCAAgC,EAAE,MAAM,sBAAsB,CAAC;AAExE,+EAA+E;AAC/E,mEAAmE;AACnE,OAAO,EAAE,wBAAwB,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAGhF,oEAAoE;AACpE,uEAAuE;AACvE,6BAA6B;AAC7B,OAAO,EACL,qBAAqB,EACrB,yBAAyB,EACzB,wBAAwB,EACxB,kBAAkB,EAClB,OAAO,EACP,gBAAgB,EAChB,wBAAwB,GACzB,MAAM,qBAAqB,CAAC;AAG7B,yEAAyE;AACzE,0EAA0E;AAC1E,4EAA4E;AAC5E,iDAAiD;AACjD,OAAO,EACL,mBAAmB,EACnB,SAAS,EACT,iBAAiB,EACjB,gBAAgB,EAChB,aAAa,EACb,SAAS,EACT,gBAAgB,EAChB,SAAS,EACT,iBAAiB,EACjB,mBAAmB,EACnB,qBAAqB,EACrB,oBAAoB,EACpB,WAAW,EACX,uBAAuB,GACxB,MAAM,oBAAoB,CAAC"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * @fileoverview Shared TypeScript type-checked Program service.
3
+ *
4
+ * The one canonical place that builds a real `ts.Program` + bound
5
+ * `TypeChecker` over an explicit file set, so type-aware fitness checks can ask
6
+ * the compiler for a value's ACTUAL type instead of guessing from its name.
7
+ * Lives here (per ADR-0010, `@opensip-cli/lang-typescript` is the single TS
8
+ * parse/AST substrate consumed by both fitness checks and the graph adapter) so
9
+ * the dependency on the `typescript` compiler stays isolated to this package —
10
+ * `checks-typescript` already depends on this barrel.
11
+ *
12
+ * This module is STATELESS: it constructs a fresh Program per call. Per-run
13
+ * sharing/caching (build one Program per `fit` run, reused by every type-aware
14
+ * TS check) is the consumer's responsibility (the fitness engine hoists it onto
15
+ * a per-run scope slot) — keeping this layer a pure, testable builder.
16
+ *
17
+ * Cost (measured on a ~900-file first-party corpus, default V8 heap): ~1s
18
+ * cold-start, <0.6 GB RSS (the D2 type-aware-null-safety spec's P0 benchmark).
19
+ */
20
+ import * as ts from 'typescript';
21
+ /** A built Program with its bound checker and a path-keyed SourceFile lookup. */
22
+ export interface TypeCheckedProgram {
23
+ readonly program: ts.Program;
24
+ readonly checker: ts.TypeChecker;
25
+ /** The tsconfig the Program was anchored to, or `undefined` if none was found. */
26
+ readonly tsconfigPath: string | undefined;
27
+ /**
28
+ * Resolve a SourceFile by absolute path (as passed in `rootFiles`). Declared
29
+ * as a bound property (arrow), not a method, so callers may safely destructure
30
+ * it off the returned object.
31
+ */
32
+ readonly getSourceFile: (absPath: string) => ts.SourceFile | undefined;
33
+ }
34
+ /** Options for {@link createTypeCheckedProgram}: where to resolve the tsconfig. */
35
+ export interface CreateTypeCheckedProgramOptions {
36
+ /** Directory to resolve a tsconfig from when `tsconfigPath` is not given. */
37
+ readonly projectRoot: string;
38
+ /** Explicit tsconfig path (absolute, or relative to `projectRoot`). */
39
+ readonly tsconfigPath?: string;
40
+ }
41
+ /**
42
+ * Build a type-checked Program over `rootFiles` (absolute on-disk paths). The
43
+ * binder is forced eagerly via `getTypeChecker()` — the dominant cost — so both
44
+ * `node.parent` chains and symbol tables are populated before the caller walks.
45
+ * The Program reads source bytes from disk itself; do NOT pre-transform input.
46
+ */
47
+ export declare function createTypeCheckedProgram(rootFiles: readonly string[], opts: CreateTypeCheckedProgramOptions): TypeCheckedProgram;
48
+ /**
49
+ * True when `type` CONCRETELY includes `null` or `undefined`. Fail-open by
50
+ * construction: `any`/`unknown`/error/unresolved types have no Null/Undefined
51
+ * union member, so they return `false` — a type-aware nullability check should
52
+ * never flag what the compiler couldn't resolve.
53
+ */
54
+ export declare function isTypeNullable(type: ts.Type): boolean;
55
+ //# sourceMappingURL=program-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"program-service.d.ts","sourceRoot":"","sources":["../src/program-service.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;GAkBG;AAKH,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AAEjC,iFAAiF;AACjF,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC,WAAW,CAAC;IACjC,kFAAkF;IAClF,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C;;;;OAIG;IACH,QAAQ,CAAC,aAAa,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,EAAE,CAAC,UAAU,GAAG,SAAS,CAAC;CACxE;AAED,mFAAmF;AACnF,MAAM,WAAW,+BAA+B;IAC9C,6EAA6E;IAC7E,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,uEAAuE;IACvE,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;CAChC;AAqDD;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACtC,SAAS,EAAE,SAAS,MAAM,EAAE,EAC5B,IAAI,EAAE,+BAA+B,GACpC,kBAAkB,CAsBpB;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,GAAG,OAAO,CAGrD"}
@@ -0,0 +1,106 @@
1
+ // @fitness-ignore-file error-handling-quality -- the ParseConfigHost.readFile shim returns undefined when TS asks about a vanished referenced file (TS treats undefined as "skip"); intentional per the ts.ParseConfigHost contract, not a silent error swallow.
2
+ // @fitness-ignore-file unbounded-memory -- readFileSync reads tsconfig.json files only, bounded by the standard TS configuration shape (mirrors graph-typescript/src/discover.ts).
3
+ /**
4
+ * @fileoverview Shared TypeScript type-checked Program service.
5
+ *
6
+ * The one canonical place that builds a real `ts.Program` + bound
7
+ * `TypeChecker` over an explicit file set, so type-aware fitness checks can ask
8
+ * the compiler for a value's ACTUAL type instead of guessing from its name.
9
+ * Lives here (per ADR-0010, `@opensip-cli/lang-typescript` is the single TS
10
+ * parse/AST substrate consumed by both fitness checks and the graph adapter) so
11
+ * the dependency on the `typescript` compiler stays isolated to this package —
12
+ * `checks-typescript` already depends on this barrel.
13
+ *
14
+ * This module is STATELESS: it constructs a fresh Program per call. Per-run
15
+ * sharing/caching (build one Program per `fit` run, reused by every type-aware
16
+ * TS check) is the consumer's responsibility (the fitness engine hoists it onto
17
+ * a per-run scope slot) — keeping this layer a pure, testable builder.
18
+ *
19
+ * Cost (measured on a ~900-file first-party corpus, default V8 heap): ~1s
20
+ * cold-start, <0.6 GB RSS (the D2 type-aware-null-safety spec's P0 benchmark).
21
+ */
22
+ import { existsSync, readFileSync } from 'node:fs';
23
+ import { dirname, isAbsolute, resolve } from 'node:path';
24
+ import * as ts from 'typescript';
25
+ /**
26
+ * Compiler options used when no tsconfig can be found (loose JS/TS checkouts).
27
+ * Conservative and strict-ish so types still resolve; the consumer's fail-open
28
+ * policy (treat unresolved/`any` as non-nullable) covers the weaker signal.
29
+ */
30
+ const FALLBACK_COMPILER_OPTIONS = {
31
+ target: ts.ScriptTarget.ES2022,
32
+ module: ts.ModuleKind.NodeNext,
33
+ moduleResolution: ts.ModuleResolutionKind.NodeNext,
34
+ strict: true,
35
+ allowJs: true,
36
+ noEmit: true,
37
+ skipLibCheck: true,
38
+ };
39
+ /** Resolve the tsconfig to anchor the Program to, or `undefined` if none. */
40
+ function resolveTsconfigPath(projectRoot, explicit) {
41
+ if (explicit !== undefined) {
42
+ const candidate = isAbsolute(explicit) ? explicit : resolve(projectRoot, explicit);
43
+ return existsSync(candidate) ? candidate : undefined;
44
+ }
45
+ return ts.findConfigFile(projectRoot, (p) => existsSync(p), 'tsconfig.json');
46
+ }
47
+ /** Load + extends-resolve a tsconfig into effective compiler options. */
48
+ function loadCompilerOptions(tsconfigPath) {
49
+ const raw = readFileSync(tsconfigPath, 'utf8');
50
+ const parsed = ts.parseConfigFileTextToJson(tsconfigPath, raw);
51
+ if (parsed.error)
52
+ return { ...FALLBACK_COMPILER_OPTIONS };
53
+ const host = {
54
+ fileExists: (p) => existsSync(p),
55
+ readDirectory: ts.sys.readDirectory.bind(ts.sys),
56
+ readFile: (p) => {
57
+ try {
58
+ return readFileSync(p, 'utf8');
59
+ }
60
+ catch {
61
+ return;
62
+ }
63
+ },
64
+ useCaseSensitiveFileNames: ts.sys.useCaseSensitiveFileNames,
65
+ };
66
+ const result = ts.parseJsonConfigFileContent(parsed.config, host, dirname(tsconfigPath), {}, tsconfigPath);
67
+ return result.options;
68
+ }
69
+ /**
70
+ * Build a type-checked Program over `rootFiles` (absolute on-disk paths). The
71
+ * binder is forced eagerly via `getTypeChecker()` — the dominant cost — so both
72
+ * `node.parent` chains and symbol tables are populated before the caller walks.
73
+ * The Program reads source bytes from disk itself; do NOT pre-transform input.
74
+ */
75
+ export function createTypeCheckedProgram(rootFiles, opts) {
76
+ const tsconfigPath = resolveTsconfigPath(opts.projectRoot, opts.tsconfigPath);
77
+ const baseOptions = tsconfigPath
78
+ ? loadCompilerOptions(tsconfigPath)
79
+ : { ...FALLBACK_COMPILER_OPTIONS };
80
+ const options = {
81
+ ...baseOptions,
82
+ // Anchor to the origin tsconfig for project-reference/rootDir resolution.
83
+ ...(tsconfigPath ? { configFilePath: tsconfigPath } : {}),
84
+ noEmit: true,
85
+ };
86
+ const program = ts.createProgram({ rootNames: [...rootFiles], options });
87
+ // Forces the binder (parent pointers + symbol tables). See module header.
88
+ const checker = program.getTypeChecker();
89
+ return {
90
+ program,
91
+ checker,
92
+ tsconfigPath,
93
+ getSourceFile: (absPath) => program.getSourceFile(absPath),
94
+ };
95
+ }
96
+ /**
97
+ * True when `type` CONCRETELY includes `null` or `undefined`. Fail-open by
98
+ * construction: `any`/`unknown`/error/unresolved types have no Null/Undefined
99
+ * union member, so they return `false` — a type-aware nullability check should
100
+ * never flag what the compiler couldn't resolve.
101
+ */
102
+ export function isTypeNullable(type) {
103
+ const members = type.isUnion() ? type.types : [type];
104
+ return members.some((m) => (m.flags & (ts.TypeFlags.Null | ts.TypeFlags.Undefined)) !== 0);
105
+ }
106
+ //# sourceMappingURL=program-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"program-service.js","sourceRoot":"","sources":["../src/program-service.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opensip-cli/lang-typescript",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "license": "Apache-2.0",
5
5
  "description": "TypeScript/JavaScript language adapter for opensip-cli",
6
6
  "keywords": [
@@ -35,7 +35,7 @@
35
35
  ],
36
36
  "dependencies": {
37
37
  "typescript": "~6.0.3",
38
- "@opensip-cli/core": "0.1.7"
38
+ "@opensip-cli/core": "0.1.8"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@types/node": "^24.13.2",