eslint-plugin-no-mistakes 0.99.0 → 0.100.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.
package/README.md CHANGED
@@ -11,14 +11,16 @@ npm install --save-dev eslint-plugin-no-mistakes
11
11
 
12
12
  For `async-call-disposition` to inspect imported callback implementations when
13
13
  the linter does not provide a TypeScript program (including Oxlint's
14
- `--type-aware` mode), also install `typescript` and
15
- `@typescript-eslint/parser` in your project:
14
+ `--type-aware` mode), also install `typescript` in your project:
16
15
 
17
16
  ```sh
18
- npm install --save-dev typescript @typescript-eslint/parser
17
+ npm install --save-dev typescript
19
18
  ```
20
19
 
21
- These are optional peer dependencies. This fallback requires TypeScript 5 or
20
+ `typescript` and `@typescript-eslint/parser` are optional peers, but only
21
+ `typescript` is required for the cross-file proof. When the ESLint parser is
22
+ absent, the proof parses supported syntax with `ts.createSourceFile`; unsupported
23
+ syntax stays unproven. This fallback requires TypeScript 5 or
22
24
  later for Bundler module resolution. With older TypeScript or missing or
23
25
  incompatible tooling, calls that rely on this cross-file proof are reported as
24
26
  unproven. Same-file proofs and explicitly configured awaiting-consumer
@@ -48,7 +50,9 @@ for the complete list.
48
50
  To cap long Vitest timeouts, enable `no-mistakes/vitest-timeout-cap` in the
49
51
  test and Vitest config files selected by your flat-config globs. It defaults to
50
52
  5,000 ms for configured test and hook timeouts and 30,000 ms for per-test and
51
- hook overrides. See the [rule documentation](../../docs/eslint-rules/vitest-timeout-cap.md)
53
+ hook overrides. Overrides above the default cap require an adjacent
54
+ `// no-mistakes-timeout-reason: <specific reason>` comment.
55
+ See the [rule documentation](../../docs/eslint-rules/vitest-timeout-cap.md)
52
56
  for options and examples.
53
57
 
54
58
  `gh-api-argument-safety` is an opt-in rule for structured endpoint fields and explicit API methods.
@@ -63,6 +67,9 @@ See [options and examples](../../docs/eslint-rules/no-comment-phrases.md).
63
67
  `forbidden-member-chains` is an opt-in rule for configured static member-access chains.
64
68
  See [options and examples](../../docs/eslint-rules/forbidden-member-chains.md).
65
69
 
70
+ `playwright-no-swallowed-visibility` is an opt-in rule for Playwright state checks whose rejection becomes a boolean literal.
71
+ See [options and examples](../../docs/eslint-rules/playwright-no-swallowed-visibility.md).
72
+
66
73
  `no-object-mutation` is an opt-in rule for shared object writes and mutating calls.
67
74
  See [options and examples](../../docs/eslint-rules/no-object-mutation.md).
68
75
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-no-mistakes",
3
- "version": "0.99.0",
3
+ "version": "0.100.0",
4
4
  "description": "ESLint and Oxlint rules that keep code static enough for no-mistakes analyzers and coding agents",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -21,17 +21,17 @@
21
21
  "test": "vitest run --coverage"
22
22
  },
23
23
  "devDependencies": {
24
- "@typescript-eslint/parser": "^8.71.1",
25
- "@vitest/coverage-v8": "^5.0.4",
26
- "eslint": "^10.12.0",
27
- "oxlint": "^1.87.0",
24
+ "@typescript-eslint/parser": "^8.72.0",
25
+ "@vitest/coverage-v8": "^5.0.5",
26
+ "eslint": "^10.13.0",
27
+ "oxlint": "^1.89.0",
28
28
  "typescript": "^6.0.3",
29
- "vitest": "^5.0.4"
29
+ "vitest": "^5.0.5"
30
30
  },
31
31
  "peerDependencies": {
32
32
  "@typescript-eslint/parser": ">=8",
33
33
  "eslint": ">=9",
34
- "typescript": ">=4.8.4"
34
+ "typescript": ">=5"
35
35
  },
36
36
  "peerDependenciesMeta": {
37
37
  "@typescript-eslint/parser": {
@@ -43,5 +43,6 @@
43
43
  },
44
44
  "engines": {
45
45
  "node": "^20.19.0 || ^22.13.0 || >=24"
46
- }
46
+ },
47
+ "//": "typescript stays on 6.0.3. TypeScript 7 makes the typescript-eslint parser throw on load. https://github.com/typescript-eslint/typescript-eslint/issues/12518 Related: https://github.com/typescript-eslint/typescript-eslint/issues/10940"
47
48
  }
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+
3
+ // Parentheses required by a parent construct are not grouping around the target.
4
+ function syntaxParen(sourceCode, node) {
5
+ // ESTree wraps an optional registration without introducing any syntax.
6
+ while (node.parent?.type === "ChainExpression") node = node.parent;
7
+ const parent = node.parent;
8
+ if (!parent) return null;
9
+ if (["CallExpression", "NewExpression"].includes(parent.type)) {
10
+ return sourceCode.getTokenAfter(
11
+ parent.typeArguments || parent.typeParameters || parent.callee,
12
+ {
13
+ filter: (token) => token.value === "(",
14
+ },
15
+ );
16
+ }
17
+ if (
18
+ [
19
+ "IfStatement",
20
+ "WhileStatement",
21
+ "SwitchStatement",
22
+ "WithStatement",
23
+ "ImportExpression",
24
+ ].includes(parent.type)
25
+ )
26
+ return sourceCode.getTokenAfter(sourceCode.getFirstToken(parent));
27
+ if (parent.type === "DoWhileStatement")
28
+ return sourceCode.getTokenAfter(sourceCode.getTokenAfter(parent.body));
29
+ return null;
30
+ }
31
+
32
+ // Only comments, whitespace and grouping may separate an annotation and its target.
33
+ // A comment trailing another statement belongs to that statement, never the target.
34
+ function adjacentAnnotations(sourceCode, node, marker) {
35
+ const comments = [];
36
+ let left = node;
37
+ let right = node;
38
+ const boundary = syntaxParen(sourceCode, node);
39
+ for (;;) {
40
+ const previous = sourceCode.getTokenBefore(left, { includeComments: true });
41
+ if (!previous || left.loc.start.line - previous.loc.end.line > 1) break;
42
+ if (["Line", "Block"].includes(previous.type)) {
43
+ comments.push(previous);
44
+ left = previous;
45
+ continue;
46
+ }
47
+ const closing = sourceCode.getTokenAfter(right);
48
+ if (
49
+ previous.value !== "(" ||
50
+ closing?.value !== ")" ||
51
+ previous.range[0] === boundary?.range[0]
52
+ )
53
+ break;
54
+ left = previous;
55
+ right = closing;
56
+ }
57
+ const result = [];
58
+ for (const comment of comments) {
59
+ const previous = sourceCode.getTokenBefore(comment);
60
+ if (
61
+ previous?.loc.end.line === comment.loc.start.line &&
62
+ !["(", "[", "{", ",", ":"].includes(previous.value)
63
+ )
64
+ break;
65
+ for (const text of comment.value.split(/\r?\n/)) {
66
+ const normalized = text.replace(/^\s*\*?\s?/, "");
67
+ if (normalized.startsWith(marker)) result.push(normalized.slice(marker.length).trim());
68
+ }
69
+ }
70
+ return result;
71
+ }
72
+ module.exports = { adjacentAnnotations };
package/src/index.js CHANGED
@@ -34,6 +34,7 @@ const rules = {
34
34
  "playwright-no-empty": require("./rules/playwright-no-empty"),
35
35
  "playwright-no-hoisted-unique-token": require("./rules/playwright-no-hoisted-unique-token"),
36
36
  "playwright-no-raw-scroll-pagination": require("./rules/playwright-no-raw-scroll-pagination"),
37
+ "playwright-no-swallowed-visibility": require("./rules/playwright-no-swallowed-visibility"),
37
38
  "playwright-prefer-get-by-test-id": require("./rules/playwright-prefer-get-by-test-id"),
38
39
  "playwright-require-exported-component-attribute": require("./rules/playwright-require-exported-component-attribute"),
39
40
  "playwright-require-interactive-test-id": require("./rules/playwright-require-interactive-test-id"),
@@ -19,6 +19,8 @@ const { createTargetMatcher } = require("./async-targets");
19
19
  const { promiseChainFrom } = require("./async-disposition-observers");
20
20
  const { createAwaitingConsumers } = require("./async-disposition-consumers");
21
21
  const { createPromiseHandoff } = require("./async-promise-handoff");
22
+ const { possibleValueType } = require("./async-source-candidates");
23
+ const { sourcesUnavailable, withSourceOwner } = require("./async-source-owner");
22
24
 
23
25
  function isDisposed(node, context, consumers, seen = new Set(), catchProven = true) {
24
26
  if (seen.has(node)) return false;
@@ -43,7 +45,7 @@ function isDisposed(node, context, consumers, seen = new Set(), catchProven = tr
43
45
  );
44
46
  }
45
47
 
46
- module.exports = rule(
48
+ const dispositionRule = rule(
47
49
  {
48
50
  type: "problem",
49
51
  docs: {
@@ -60,7 +62,11 @@ module.exports = rule(
60
62
  },
61
63
  (context) => {
62
64
  const matcher = createTargetMatcher(context);
63
- const sourceFacts = { typed: new WeakMap(), sources: null, targets: matcher.targets };
65
+ const sourceFacts = {
66
+ typed: new WeakMap(),
67
+ sources: sourcesUnavailable(context) ? false : null,
68
+ targets: matcher.targets,
69
+ };
64
70
  const { promiseSlotHandoff, promiseTypedTargetCall } = createPromiseHandoff(
65
71
  context,
66
72
  sourceFacts,
@@ -89,9 +95,13 @@ module.exports = rule(
89
95
  ),
90
96
  CallExpression(node) {
91
97
  if (suppressed(node)) return;
98
+ const target = matcher.isTargetCall(node);
99
+ // Unrelated calls cannot need a callback-body or imported type proof.
100
+ if (!target && (sourceFacts.sources === false || !possibleValueType(node.callee, context)))
101
+ return;
92
102
  // Disposed calls need no cross-file type lookup, including local/configured JS consumers.
93
103
  if (isDisposed(node, context, consumers)) return;
94
- if (!matcher.isTargetCall(node) && !promiseTypedTargetCall(node)) return;
104
+ if (!target && !promiseTypedTargetCall(node)) return;
95
105
  context.report({
96
106
  node,
97
107
  messageId: "disposition",
@@ -111,3 +121,5 @@ module.exports = rule(
111
121
  };
112
122
  },
113
123
  );
124
+
125
+ module.exports = withSourceOwner(dispositionRule);
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
 
3
- const { resolveVariable } = require("./async-target-bindings");
3
+ const { resolveVariable, resolveRuntimeVariable } = require("./async-target-bindings");
4
4
  const { propertiesWritten } = require("./async-disposition-mocks");
5
5
 
6
6
  function sameKey(first, key, context) {
@@ -34,18 +34,18 @@ function builtinMap(node, context) {
34
34
  references.some(
35
35
  (ref) =>
36
36
  ["eval", "Function"].includes(ref.identifier.name) &&
37
- !resolveVariable(ref.identifier, context)?.defs.length,
37
+ !resolveRuntimeVariable(ref.identifier, context)?.defs.length,
38
38
  )
39
39
  )
40
40
  return false;
41
- const binding = resolveVariable(node, context);
41
+ const binding = resolveRuntimeVariable(node, context);
42
42
  // Do not infer built-in semantics after the constructor/prototype escapes to
43
43
  // reflective APIs or unknown consumers, whose effects are not modeled here.
44
44
  if (
45
45
  references.some(
46
46
  (ref) =>
47
47
  ref.identifier.name === "Map" &&
48
- resolveVariable(ref.identifier, context) === binding &&
48
+ resolveRuntimeVariable(ref.identifier, context) === binding &&
49
49
  !(
50
50
  ref.identifier.parent.type === "NewExpression" &&
51
51
  ref.identifier.parent.callee === ref.identifier
@@ -56,7 +56,7 @@ function builtinMap(node, context) {
56
56
  const holders = ["globalThis", "global", "window", "self"];
57
57
  return !references.some((ref) => {
58
58
  if (!holders.includes(ref.identifier.name)) return false;
59
- if (resolveVariable(ref.identifier, context)?.defs.length) return false;
59
+ if (resolveRuntimeVariable(ref.identifier, context)?.defs.length) return false;
60
60
  if (propertiesWritten(ref.identifier, context)) return true;
61
61
  const member = ref.identifier.parent;
62
62
  if (member.type !== "MemberExpression" || member.object !== ref.identifier) return true;
@@ -2,7 +2,10 @@
2
2
 
3
3
  const { isPromiseMethodCall } = require("./async-disposition-bindings");
4
4
  const { unwrapExpression } = require("./async-ast");
5
- const { resolveVariable, memberPropertyName } = require("./async-target-bindings");
5
+ const {
6
+ resolveRuntimeVariable: resolveVariable,
7
+ memberPropertyName,
8
+ } = require("./async-target-bindings");
6
9
 
7
10
  function observerMutated(node, context, suffix = []) {
8
11
  function paths(value, seen = new Set()) {
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+
3
+ const { resolveVariable, stableConstInitializer } = require("./async-target-bindings");
4
+
5
+ const sourceTypeShapes = new Set([
6
+ "TSTypeReference",
7
+ "TSTypeQuery",
8
+ "TSTypeLiteral",
9
+ "TSIntersectionType",
10
+ "TSParenthesizedType",
11
+ ]);
12
+
13
+ // Mirror the shapes valueType can follow without resolving or parsing imports.
14
+ // In particular, an import or function declaration alone has no receiving type.
15
+ function possibleValueType(value, context, seen = new Set()) {
16
+ if (!value || seen.size >= 32 || seen.has(value)) return false;
17
+ const next = new Set(seen).add(value);
18
+ if (value.type === "Identifier") {
19
+ const variable = resolveVariable(value, context);
20
+ if (variable?.defs.length !== 1) return false;
21
+ const type = variable.defs[0].name?.typeAnnotation?.typeAnnotation;
22
+ // Arrays, function signatures, and primitive annotations cannot establish
23
+ // targetType or a property contract in the bounded source type model.
24
+ if (type) return sourceTypeShapes.has(type.type);
25
+ if (variable.references.some((ref) => ref.isWrite() && !ref.init)) return false;
26
+ const init = stableConstInitializer(value, context, new Set());
27
+ return possibleValueType(init, context, next);
28
+ }
29
+ // declarations.callable only resolves identifier callees, including imported
30
+ // factories with a return-type alias. Member calls and IIFEs cannot contribute.
31
+ if (value.type === "CallExpression") return !value.optional && value.callee.type === "Identifier";
32
+ return value.type === "MemberExpression" && possibleValueType(value.object, context, next);
33
+ }
34
+
35
+ module.exports = { possibleValueType };
@@ -6,14 +6,14 @@ const { unwrapExpression } = require("./async-ast");
6
6
  // Follow bounded static declarations. Missing source, cycles, and changing bindings
7
7
  // cannot establish the identity of a configured target or dependency type.
8
8
  function createDeclarations(sources) {
9
- function exported(filename, name, seen = new Set(), budget = { remaining: 128 }) {
10
- const key = `${filename}:${name}`;
11
- if (budget.remaining-- <= 0 || seen.size >= 32 || seen.has(key)) return false;
12
- seen = new Set(seen).add(key);
13
- const source = sources.load(filename);
14
- if (!source) return false;
15
- const matches = [];
9
+ function exportIndex(source) {
10
+ if (source.exportIndex) return source.exportIndex;
11
+ const names = new Map();
16
12
  const stars = [];
13
+ function add(name, match) {
14
+ if (!names.has(name)) names.set(name, []);
15
+ names.get(name).push(match);
16
+ }
17
17
  for (const statement of source.ast.body) {
18
18
  if (statement.type === "ExportAllDeclaration") {
19
19
  if (!statement.exported) stars.push(statement.source.value);
@@ -21,24 +21,41 @@ function createDeclarations(sources) {
21
21
  }
22
22
  if (statement.type !== "ExportNamedDeclaration") continue;
23
23
  const declaration = statement.declaration;
24
- if (declaration?.id?.name === name) matches.push({ local: declaration.id });
24
+ if (declaration?.id?.name) add(declaration.id.name, { local: declaration.id });
25
25
  if (declaration?.type === "VariableDeclaration")
26
26
  for (const item of declaration.declarations)
27
- if (item.id.type === "Identifier" && item.id.name === name)
28
- matches.push({ local: item.id });
27
+ if (item.id.type === "Identifier") add(item.id.name, { local: item.id });
29
28
  for (const item of statement.specifiers)
30
- if ((item.exported.name ?? item.exported.value) === name)
31
- matches.push({ local: item.local, source: statement.source });
29
+ add(item.exported.name ?? item.exported.value, {
30
+ local: item.local,
31
+ source: statement.source,
32
+ });
32
33
  }
34
+ const index = { names, stars };
35
+ source.exportIndex = index;
36
+ return index;
37
+ }
38
+
39
+ function exported(filename, name, seen = new Set(), budget = { remaining: 128 }) {
40
+ const key = `${filename}:${name}`;
41
+ if (budget.remaining-- <= 0 || seen.size >= 32 || seen.has(key)) return false;
42
+ seen = new Set(seen).add(key);
43
+ const source = sources.load(filename);
44
+ if (!source) return false;
45
+ const { names, stars } = exportIndex(source);
46
+ const matches = names.get(name) || [];
33
47
  if (matches.length > 1) return false;
34
48
  if (!matches.length) {
35
- const candidates = stars.map((specifier) => {
49
+ const found = [];
50
+ for (const specifier of stars) {
51
+ // Bound resolution work too, including cached or unresolved branches.
52
+ if (budget.remaining <= 0) return false;
36
53
  const target = sources.resolve(specifier, filename);
37
- return target ? exported(target, name, seen, budget) : false;
38
- });
39
- // An unreadable branch may also export this name; absence is not proof.
40
- if (candidates.includes(false)) return false;
41
- const found = candidates.filter(Boolean);
54
+ const candidate = target ? exported(target, name, seen, budget) : false;
55
+ // An unreadable branch may also export this name; absence is not proof.
56
+ if (candidate === false) return false;
57
+ if (candidate) found.push(candidate);
58
+ }
42
59
  return found.length > 1 ? false : found[0] || null;
43
60
  }
44
61
  const match = matches[0];
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+
3
+ function parsedSource(filename, text, parser) {
4
+ const parsed = parser.parseForESLint(text, { filePath: filename, range: true, loc: true });
5
+ function parents(node, parent) {
6
+ node.parent = parent;
7
+ for (const key of parsed.visitorKeys[node.type] || []) {
8
+ const child = node[key];
9
+ if (Array.isArray(child)) child.forEach((item) => item && parents(item, node));
10
+ else if (child) parents(child, node);
11
+ }
12
+ }
13
+ parents(parsed.ast, null);
14
+ const sourceCode = {
15
+ ...parsed,
16
+ getScope(node) {
17
+ // Every parsed node has a Program ancestor with a global scope.
18
+ for (let owner = node; ; owner = owner.parent) {
19
+ const scope = parsed.scopeManager.acquire(owner, true);
20
+ if (scope) return scope;
21
+ }
22
+ },
23
+ };
24
+ return { ast: parsed.ast, context: { sourceCode, filename } };
25
+ }
26
+
27
+ function currentSources(context, store) {
28
+ const filename = context.filename;
29
+ let main;
30
+ return {
31
+ resolve: store.resolve,
32
+ load(path) {
33
+ // Oxlint reuses its Context/SourceCode/AST. Keep this overlay per file;
34
+ // the shared store contains only independently parsed imported sources.
35
+ return path === filename
36
+ ? (main ||= { ast: context.sourceCode.ast, context })
37
+ : store.load(path);
38
+ },
39
+ };
40
+ }
41
+
42
+ module.exports = { parsedSource, currentSources };
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+
3
+ const { readFileSync, realpathSync } = require("node:fs");
4
+ const { dirname, resolve } = require("node:path");
5
+
6
+ // The public npm CLI calls native lint once. Editors, fix passes, and unknown
7
+ // hosts have no invocation boundary available to a JS plugin, so stay per-file.
8
+ function oneShotCli(entry = process.argv[1], args = process.argv.slice(2)) {
9
+ if (!entry || args.some((arg) => /^--(?:lsp|watch)(?:=|$)/.test(arg) || arg.startsWith("--fix")))
10
+ return false;
11
+ try {
12
+ const filename = realpathSync(entry);
13
+ const root = resolve(dirname(filename), "..");
14
+ const manifest = JSON.parse(readFileSync(resolve(root, "package.json"), "utf8"));
15
+ return (
16
+ manifest.name === "oxlint" &&
17
+ typeof manifest.bin?.oxlint === "string" &&
18
+ realpathSync(resolve(root, manifest.bin.oxlint)) === filename
19
+ );
20
+ } catch {
21
+ return false;
22
+ }
23
+ }
24
+
25
+ const activeOwners = new WeakMap();
26
+
27
+ function sourceOwner(context) {
28
+ const owner = activeOwners.get(context);
29
+ if (!owner) return null;
30
+ owner.shared ??= oneShotCli();
31
+ return owner.shared ? owner : null;
32
+ }
33
+
34
+ function sourcesUnavailable(context) {
35
+ return activeOwners.get(context)?.sources === false;
36
+ }
37
+
38
+ function withSourceOwner(rule) {
39
+ rule.createOnce = (context) => {
40
+ const owner = { sources: null };
41
+ let visitor;
42
+ return {
43
+ ...Object.fromEntries(
44
+ [
45
+ "Program",
46
+ "ImportDeclaration",
47
+ "TSImportEqualsDeclaration",
48
+ "VariableDeclarator",
49
+ "CallExpression",
50
+ ].map((key) => [key, (node) => visitor[key]?.(node)]),
51
+ ),
52
+ before() {
53
+ activeOwners.set(context, owner);
54
+ try {
55
+ visitor = rule.create(context);
56
+ if (Object.keys(visitor).length === 0) {
57
+ visitor = null;
58
+ activeOwners.delete(context);
59
+ return false;
60
+ }
61
+ } catch (error) {
62
+ activeOwners.delete(context);
63
+ throw error;
64
+ }
65
+ },
66
+ after() {
67
+ visitor = null;
68
+ activeOwners.delete(context);
69
+ },
70
+ };
71
+ };
72
+ return rule;
73
+ }
74
+
75
+ module.exports = { oneShotCli, sourceOwner, sourcesUnavailable, withSourceOwner };
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+
3
+ const { dirname } = require("node:path");
4
+ const { parsedSource } = require("./async-source-files");
5
+
6
+ function createSourceSession(ts, parser) {
7
+ // Legacy resolution may inspect `main` while runtime `exports` selects another body.
8
+ if (
9
+ ts.ModuleResolutionKind.Bundler === undefined ||
10
+ typeof ts.createModuleResolutionCache !== "function"
11
+ )
12
+ return null;
13
+ const files = new Map();
14
+ const configs = new Map();
15
+ const configFiles = new Map();
16
+ const resolved = new Map();
17
+ const resolutionCaches = new Map();
18
+ const host = { ...ts.sys };
19
+ for (const method of ["readFile", "fileExists", "directoryExists", "realpath"]) {
20
+ if (!ts.sys[method]) continue;
21
+ const results = new Map();
22
+ host[method] = (path) => {
23
+ if (!results.has(path)) results.set(path, ts.sys[method](path));
24
+ return results.get(path);
25
+ };
26
+ }
27
+ function compilerOptions(config, path) {
28
+ const parsed = ts.parseJsonConfigFileContent(
29
+ config,
30
+ { ...host, readDirectory: () => [] },
31
+ dirname(path),
32
+ undefined,
33
+ path,
34
+ );
35
+ return parsed.errors.some((error) => error.code !== 18003) ? null : parsed.options;
36
+ }
37
+ function options(filename) {
38
+ const directory = dirname(filename);
39
+ if (!configs.has(directory)) {
40
+ const path = ts.findConfigFile(directory, host.fileExists);
41
+ if (!configFiles.has(path)) {
42
+ const config = path && ts.readConfigFile(path, host.readFile);
43
+ configFiles.set(
44
+ path,
45
+ config?.error
46
+ ? null
47
+ : config
48
+ ? compilerOptions(config.config, path)
49
+ : { moduleResolution: ts.ModuleResolutionKind.Bundler },
50
+ );
51
+ }
52
+ configs.set(directory, configFiles.get(path));
53
+ }
54
+ return configs.get(directory);
55
+ }
56
+ function resolve(source, filename) {
57
+ if (!resolved.has(filename)) resolved.set(filename, new Map());
58
+ const imports = resolved.get(filename);
59
+ if (!imports.has(source)) {
60
+ const config = options(filename);
61
+ let target = null;
62
+ if (config && !config.noResolve) {
63
+ // Resolver caches must not mix paths/baseUrl from distinct tsconfigs.
64
+ if (!resolutionCaches.has(config))
65
+ resolutionCaches.set(
66
+ config,
67
+ ts.createModuleResolutionCache(process.cwd(), (path) => path, config),
68
+ );
69
+ target = ts.resolveModuleName(source, filename, config, host, resolutionCaches.get(config))
70
+ .resolvedModule?.resolvedFileName;
71
+ }
72
+ imports.set(source, target);
73
+ }
74
+ return imports.get(source);
75
+ }
76
+ function load(filename) {
77
+ if (!files.has(filename)) {
78
+ files.set(filename, null);
79
+ const text = host.readFile(filename);
80
+ try {
81
+ files.set(filename, parsedSource(filename, text, parser));
82
+ } catch {
83
+ // Missing, ambient, or unsupported source never establishes an awaiting contract.
84
+ }
85
+ }
86
+ return files.get(filename);
87
+ }
88
+ return { resolve, load };
89
+ }
90
+
91
+ module.exports = { createSourceSession };