@ttsc/lint 0.24.0 → 0.26.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 +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.js +161 -46
- package/lib/index.js.map +1 -1
- package/lib/internal/configEvaluatorFailure.d.ts +24 -20
- package/lib/internal/configEvaluatorFailure.js +41 -54
- package/lib/internal/configEvaluatorFailure.js.map +1 -1
- package/lib/structures/rules/ITtscLintFunctionalRuleOptions.d.ts +73 -16
- package/linthost/config.go +154 -64
- package/linthost/config_format.go +7 -0
- package/linthost/directives.go +4 -7
- package/linthost/fix.go +6 -4
- package/linthost/rule_codes.json +1 -0
- package/linthost/rules_format_brace_continuation.go +267 -0
- package/linthost/rules_format_clause_join.go +289 -40
- package/linthost/rules_format_parameter_properties.go +9 -21
- package/linthost/rules_format_whitespace.go +5 -1
- package/linthost/rules_functional.go +248 -25
- package/linthost/rules_ts_extra.go +7 -5
- package/package.json +2 -2
- package/rule/rule.go +21 -9
- package/src/index.ts +163 -53
- package/src/internal/configEvaluatorFailure.ts +35 -64
- package/src/structures/rules/ITtscLintFunctionalRuleOptions.ts +73 -16
package/src/index.ts
CHANGED
|
@@ -8,9 +8,7 @@ import path from "node:path";
|
|
|
8
8
|
import { pathToFileURL } from "node:url";
|
|
9
9
|
|
|
10
10
|
import {
|
|
11
|
-
|
|
12
|
-
CONFIG_EVALUATOR_STATUS_FD,
|
|
13
|
-
configEvaluatorBoundaryEnvironment,
|
|
11
|
+
configEvaluatorFailureReason,
|
|
14
12
|
configEvaluatorProcessFailure,
|
|
15
13
|
} from "./internal/configEvaluatorFailure";
|
|
16
14
|
import type { ITtscLintPlugin, ITtscLintPluginConfig } from "./structures";
|
|
@@ -630,33 +628,68 @@ const hooks = registerHooks({
|
|
|
630
628
|
},
|
|
631
629
|
});
|
|
632
630
|
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
631
|
+
// Wrapped and settled explicitly rather than written as a top-level await.
|
|
632
|
+
// The loader tsconfig's "module" now follows the config's own package, and
|
|
633
|
+
// TS1378 rejects top-level await under a CommonJS module option however this
|
|
634
|
+
// .mts file emits. The trailing catch is what a top-level await gave for
|
|
635
|
+
// free: without it a throw from the finally would leave the promise
|
|
636
|
+
// unsettled instead of failing the load.
|
|
637
|
+
(async () => {
|
|
638
|
+
try {
|
|
639
|
+
const importedConfig = configLocation.toLowerCase().endsWith(".json")
|
|
640
|
+
? JSON.parse(fs.readFileSync(configLocation, "utf8").replace(/^\uFEFF/, ""))
|
|
641
|
+
: await import(configUrl);
|
|
642
|
+
const current = await resolveConfig(importedConfig, true);
|
|
643
|
+
const pluginMaps = collectPluginObjects(current);
|
|
644
|
+
const entries: Array<{ namespace: string; source: string }> = [];
|
|
645
|
+
for (const map of pluginMaps) {
|
|
646
|
+
for (const [namespace, value] of Object.entries(map)) {
|
|
647
|
+
const source = extractPluginSource(value);
|
|
648
|
+
if (source === undefined || source.length === 0) {
|
|
649
|
+
throw new Error(
|
|
650
|
+
\`contributor \${JSON.stringify(namespace)} must resolve to an object with a non-empty "source" string\`,
|
|
651
|
+
);
|
|
652
|
+
}
|
|
653
|
+
entries.push({ namespace, source });
|
|
647
654
|
}
|
|
648
|
-
entries.push({ namespace, source });
|
|
649
655
|
}
|
|
656
|
+
fs.writeFileSync(outputPath, JSON.stringify({
|
|
657
|
+
dependencies: finalizeDependencies(),
|
|
658
|
+
entries,
|
|
659
|
+
}), "utf8");
|
|
660
|
+
} catch (error) {
|
|
661
|
+
reportLoaderFailure(error);
|
|
662
|
+
} finally {
|
|
663
|
+
hooks.deregister();
|
|
650
664
|
}
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
665
|
+
})().catch((error) => {
|
|
666
|
+
// Reached only when the finally above throws: the catch already ends the
|
|
667
|
+
// process, so this is the deregistration's own failure, not the config's.
|
|
668
|
+
reportLoaderFailure(error);
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
// reportLoaderFailure ends this loader on an error it can name, on both of the
|
|
672
|
+
// channels the parent uses.
|
|
673
|
+
//
|
|
674
|
+
// The stack is for a reader and streams to stderr as it is written. The reason
|
|
675
|
+
// is a fact about the user's config that a caller has to act on, so it travels
|
|
676
|
+
// as data through the result file the parent already reads. Only a well-formed
|
|
677
|
+
// envelope is honoured there, so a partially written or unrelated file leaves
|
|
678
|
+
// the process status to speak for itself.
|
|
679
|
+
function reportLoaderFailure(error: unknown): never {
|
|
680
|
+
// The trailing newline ends the stack as a line of its own. Without it the
|
|
681
|
+
// parent's own message, written to this same stream, starts mid-line.
|
|
682
|
+
process.stderr.write((error instanceof Error && error.stack ? error.stack : String(error)) + "\\n");
|
|
683
|
+
try {
|
|
684
|
+
fs.writeFileSync(
|
|
685
|
+
outputPath,
|
|
686
|
+
JSON.stringify({ __ttscLoaderError: error instanceof Error ? error.message : String(error) }),
|
|
687
|
+
"utf8",
|
|
688
|
+
);
|
|
689
|
+
} catch {
|
|
690
|
+
// A reason that cannot be written leaves the exit status as the report.
|
|
691
|
+
}
|
|
692
|
+
return process.exit(1);
|
|
660
693
|
}
|
|
661
694
|
|
|
662
695
|
function isObject(value: unknown): value is Record<string, unknown> {
|
|
@@ -1702,7 +1735,13 @@ function evaluateTtsxConfigPlugins(
|
|
|
1702
1735
|
allowImportingTsExtensions: true,
|
|
1703
1736
|
allowJs: true,
|
|
1704
1737
|
checkJs: false,
|
|
1705
|
-
module:
|
|
1738
|
+
// The config is a Node module, so Node's rule decides its format:
|
|
1739
|
+
// the nearest `package.json` `type` above it. Hardcoding one answer
|
|
1740
|
+
// ran every ambiguous `.ts` config as ESM and broke `__dirname` in
|
|
1741
|
+
// an ordinary CommonJS package (#1068). `moduleResolution` stays
|
|
1742
|
+
// `bundler`, which tsgo accepts for both kinds, so extensionless
|
|
1743
|
+
// relative imports keep resolving either way.
|
|
1744
|
+
module: configModuleOption(configPath),
|
|
1706
1745
|
moduleResolution: "bundler",
|
|
1707
1746
|
noImplicitAny: false,
|
|
1708
1747
|
outDir: path.join(tempDir, "out").replace(/\\/g, "/"),
|
|
@@ -1711,6 +1750,12 @@ function evaluateTtsxConfigPlugins(
|
|
|
1711
1750
|
skipLibCheck: true,
|
|
1712
1751
|
strict: false,
|
|
1713
1752
|
target: "ES2022",
|
|
1753
|
+
// TypeScript 7 includes no ambient type package unless `types` asks
|
|
1754
|
+
// for it, and this Program extends nothing, so without the wildcard
|
|
1755
|
+
// a config could not name a single Node global (#1068). The loader
|
|
1756
|
+
// directory links the config's nearest `node_modules`, so the
|
|
1757
|
+
// default `typeRoots` walk finds exactly what the project installed.
|
|
1758
|
+
types: ["*"],
|
|
1714
1759
|
},
|
|
1715
1760
|
files: [
|
|
1716
1761
|
loaderPath.replace(/\\/g, "/"),
|
|
@@ -1749,28 +1794,29 @@ function evaluateTtsxConfigPlugins(
|
|
|
1749
1794
|
}
|
|
1750
1795
|
const env = {
|
|
1751
1796
|
...nodeConfigLoaderEnv(configPath),
|
|
1752
|
-
...configEvaluatorBoundaryEnvironment(),
|
|
1753
1797
|
};
|
|
1754
1798
|
const command = ttsxThroughNodeIfNeeded(ttsxBinary);
|
|
1755
1799
|
const result = spawnSync(command.binary, [...command.prefix, ...args], {
|
|
1756
1800
|
cwd: tempDir,
|
|
1757
1801
|
env,
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
...Array.from(
|
|
1765
|
-
{ length: CONFIG_EVALUATOR_STATUS_FD - 2 },
|
|
1766
|
-
() => "pipe" as const,
|
|
1767
|
-
),
|
|
1768
|
-
],
|
|
1802
|
+
// Both child streams are human output, and they go straight to this
|
|
1803
|
+
// process's stderr as they are written. Nothing is collected here: the
|
|
1804
|
+
// parent's stdout is reserved for compiler JSON and LSP frames, and
|
|
1805
|
+
// buffering the child only to replay it afterwards is what forced an
|
|
1806
|
+
// invented output ceiling and made a long evaluation print nothing at all.
|
|
1807
|
+
stdio: ["ignore", 2, 2],
|
|
1769
1808
|
windowsHide: true,
|
|
1770
1809
|
});
|
|
1771
|
-
forwardConfigEvaluatorStreams(result.stdout, result.stderr);
|
|
1772
1810
|
const processFailure = configEvaluatorProcessFailure(result, configPath);
|
|
1773
|
-
if (processFailure)
|
|
1811
|
+
if (processFailure) {
|
|
1812
|
+
// The evaluator's stack already reached the user's stderr as it ran. What
|
|
1813
|
+
// it could not put there is a reason a caller can act on, so that arrives
|
|
1814
|
+
// through the result file instead.
|
|
1815
|
+
const reason = configEvaluatorFailureReason(outputPath);
|
|
1816
|
+
throw reason === ""
|
|
1817
|
+
? processFailure
|
|
1818
|
+
: new Error(`${processFailure.message}\n${reason}`);
|
|
1819
|
+
}
|
|
1774
1820
|
let payload: {
|
|
1775
1821
|
dependencies?: ConfigDependencyFingerprint[];
|
|
1776
1822
|
entries?: ConfigPluginEntry[];
|
|
@@ -1841,20 +1887,10 @@ function evaluateTtsxConfigPlugins(
|
|
|
1841
1887
|
}
|
|
1842
1888
|
return { dependencies, entries };
|
|
1843
1889
|
} finally {
|
|
1844
|
-
|
|
1890
|
+
removeEvaluationTempDir(tempDir);
|
|
1845
1891
|
}
|
|
1846
1892
|
}
|
|
1847
1893
|
|
|
1848
|
-
function forwardConfigEvaluatorStreams(
|
|
1849
|
-
stdout: string | null | undefined,
|
|
1850
|
-
stderr: string | null | undefined,
|
|
1851
|
-
): void {
|
|
1852
|
-
// Both child streams are human output. Parent stdout is reserved for compiler
|
|
1853
|
-
// JSON or LSP frames, so even a user console.log is redirected.
|
|
1854
|
-
if (stdout) process.stderr.write(stdout);
|
|
1855
|
-
if (stderr) process.stderr.write(stderr);
|
|
1856
|
-
}
|
|
1857
|
-
|
|
1858
1894
|
// ────────────────────────────────────────────────────────────────────────────
|
|
1859
1895
|
// Config cache (shared with the Go sidecar — packages/lint/linthost/config.go)
|
|
1860
1896
|
// ────────────────────────────────────────────────────────────────────────────
|
|
@@ -2268,6 +2304,64 @@ function findNearestNodeModules(start: string): string | undefined {
|
|
|
2268
2304
|
}
|
|
2269
2305
|
}
|
|
2270
2306
|
|
|
2307
|
+
/**
|
|
2308
|
+
* The loader tsconfig's `module` for a given config file: the module kind Node
|
|
2309
|
+
* would give the file itself.
|
|
2310
|
+
*
|
|
2311
|
+
* An explicit `.cts`/`.cjs` or `.mts`/`.mjs` extension already decides the emit
|
|
2312
|
+
* format on its own, so those keep the ES-module setting and let the extension
|
|
2313
|
+
* win — the same precedence tsgo applies. Everything ambiguous walks up for the
|
|
2314
|
+
* nearest `package.json` `type`, exactly as Node does when it loads the file.
|
|
2315
|
+
*/
|
|
2316
|
+
function configModuleOption(configPath: string): string {
|
|
2317
|
+
const extension = path.extname(configPath).toLowerCase();
|
|
2318
|
+
if (extension !== ".ts" && extension !== ".tsx" && extension !== ".js") {
|
|
2319
|
+
return "ESNext";
|
|
2320
|
+
}
|
|
2321
|
+
return nearestPackageType(configPath) === "commonjs" ? "CommonJS" : "ESNext";
|
|
2322
|
+
}
|
|
2323
|
+
|
|
2324
|
+
/**
|
|
2325
|
+
* Nearest `package.json` `"type"` at or above `configPath`, mirroring Node's
|
|
2326
|
+
* package-scope lookup: the walk stops at the **first** manifest it finds, and
|
|
2327
|
+
* a manifest that declares no `"type"` means CommonJS rather than a reason to
|
|
2328
|
+
* keep climbing. Reaching the filesystem root without any manifest also means
|
|
2329
|
+
* CommonJS.
|
|
2330
|
+
*/
|
|
2331
|
+
function nearestPackageType(configPath: string): "commonjs" | "module" {
|
|
2332
|
+
let dir = path.dirname(path.resolve(configPath));
|
|
2333
|
+
for (;;) {
|
|
2334
|
+
// One read rather than a stat-then-read: an unreadable entry and a missing
|
|
2335
|
+
// one are the same answer here — keep walking — and the Go loaders resolve
|
|
2336
|
+
// it the same way, so the four implementations of this rule cannot drift.
|
|
2337
|
+
let raw: string | undefined;
|
|
2338
|
+
try {
|
|
2339
|
+
raw = fs.readFileSync(path.join(dir, "package.json"), "utf8");
|
|
2340
|
+
} catch {
|
|
2341
|
+
raw = undefined;
|
|
2342
|
+
}
|
|
2343
|
+
if (raw !== undefined) {
|
|
2344
|
+
try {
|
|
2345
|
+
const manifest: unknown = JSON.parse(raw);
|
|
2346
|
+
const type =
|
|
2347
|
+
typeof manifest === "object" && manifest !== null
|
|
2348
|
+
? (manifest as { type?: unknown }).type
|
|
2349
|
+
: undefined;
|
|
2350
|
+
return type === "module" ? "module" : "commonjs";
|
|
2351
|
+
} catch {
|
|
2352
|
+
// A manifest that does not parse still bounds the package scope; Node
|
|
2353
|
+
// refuses to look past it, and CommonJS is the format it defaults to.
|
|
2354
|
+
return "commonjs";
|
|
2355
|
+
}
|
|
2356
|
+
}
|
|
2357
|
+
const parent = path.dirname(dir);
|
|
2358
|
+
if (parent === dir) {
|
|
2359
|
+
return "commonjs";
|
|
2360
|
+
}
|
|
2361
|
+
dir = parent;
|
|
2362
|
+
}
|
|
2363
|
+
}
|
|
2364
|
+
|
|
2271
2365
|
function linkNearestNodeModules(tempDir: string, sourceDir: string): void {
|
|
2272
2366
|
const nodeModules = findNearestNodeModules(sourceDir);
|
|
2273
2367
|
if (!nodeModules) return;
|
|
@@ -2400,3 +2494,19 @@ function ttsxThroughNodeIfNeeded(binary: string): {
|
|
|
2400
2494
|
}
|
|
2401
2495
|
return { binary, prefix: [] };
|
|
2402
2496
|
}
|
|
2497
|
+
|
|
2498
|
+
/**
|
|
2499
|
+
* Remove an evaluation temp directory without letting cleanup replace a result.
|
|
2500
|
+
*
|
|
2501
|
+
* This runs from a `finally`, so a throw here would surface instead of the
|
|
2502
|
+
* evaluation's own outcome — and on Windows a grandchild that inherited a
|
|
2503
|
+
* handle, or a scanner holding the file, can make removal fail. Leaving bytes
|
|
2504
|
+
* in the system temp directory is by far the lesser outcome.
|
|
2505
|
+
*/
|
|
2506
|
+
function removeEvaluationTempDir(directory: string): void {
|
|
2507
|
+
try {
|
|
2508
|
+
fs.rmSync(directory, { force: true, recursive: true });
|
|
2509
|
+
} catch {
|
|
2510
|
+
// Best effort.
|
|
2511
|
+
}
|
|
2512
|
+
}
|
|
@@ -1,51 +1,31 @@
|
|
|
1
|
-
|
|
2
|
-
export const CONFIG_EVALUATOR_TIMEOUT_MS = 60_000;
|
|
3
|
-
export const CONFIG_EVALUATOR_TEARDOWN_GRACE_MS = 5_000;
|
|
4
|
-
export const CONFIG_EVALUATOR_STATUS_FD = 3;
|
|
5
|
-
export const CONFIG_EVALUATOR_PROCESS_OPTIONS = Object.freeze({
|
|
6
|
-
killSignal: "SIGKILL" as const,
|
|
7
|
-
maxBuffer: CONFIG_EVALUATOR_MAX_BUFFER,
|
|
8
|
-
timeout: CONFIG_EVALUATOR_TIMEOUT_MS + CONFIG_EVALUATOR_TEARDOWN_GRACE_MS,
|
|
9
|
-
});
|
|
1
|
+
import fs from "node:fs";
|
|
10
2
|
|
|
11
3
|
interface ConfigEvaluatorProcessResult {
|
|
12
4
|
error?: Error;
|
|
13
|
-
output?: readonly (string | null)[] | null;
|
|
14
5
|
signal: NodeJS.Signals | null;
|
|
15
6
|
status: number | null;
|
|
16
|
-
stderr: string | null | undefined;
|
|
17
7
|
}
|
|
18
8
|
|
|
19
9
|
/**
|
|
20
10
|
* Classify the ways the isolated lint-config evaluator can stop.
|
|
21
11
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
12
|
+
* The evaluator writes the child's own output straight to this process's
|
|
13
|
+
* stderr, so a diagnostic has already reached the user by the time anything
|
|
14
|
+
* here runs. What is left to say is only how the process ended: it never
|
|
15
|
+
* launched, something outside killed it, or it exited non-zero after printing
|
|
16
|
+
* its own reason.
|
|
17
|
+
*
|
|
18
|
+
* Nothing is bounded here — not time, not output. Both were the compiler
|
|
19
|
+
* deciding, on numbers nobody chose for this machine, that a user's own config
|
|
20
|
+
* had run too long or said too much. A slow config is a slow build the user can
|
|
21
|
+
* watch and interrupt; a loud one is output they asked for. Neither is this
|
|
22
|
+
* process's memory to spend either, because the child's streams are no longer
|
|
23
|
+
* collected into it.
|
|
28
24
|
*/
|
|
29
25
|
export function configEvaluatorProcessFailure(
|
|
30
26
|
result: ConfigEvaluatorProcessResult,
|
|
31
27
|
configPath: string,
|
|
32
28
|
): Error | undefined {
|
|
33
|
-
const code = (result.error as NodeJS.ErrnoException | undefined)?.code;
|
|
34
|
-
const nestedCode = result.output?.[CONFIG_EVALUATOR_STATUS_FD]?.trim() ?? "";
|
|
35
|
-
if (code === "ETIMEDOUT" || nestedCode === "ETIMEDOUT") {
|
|
36
|
-
return new Error(
|
|
37
|
-
`@ttsc/lint: ttsx evaluation of ${configPath} timed out after ` +
|
|
38
|
-
`${CONFIG_EVALUATOR_TIMEOUT_MS / 1_000} seconds. ` +
|
|
39
|
-
"Simplify the config or move heavy work out of top-level.",
|
|
40
|
-
);
|
|
41
|
-
}
|
|
42
|
-
if (code === "ENOBUFS" || nestedCode === "ENOBUFS") {
|
|
43
|
-
return new Error(
|
|
44
|
-
`@ttsc/lint: ttsx evaluation of ${configPath} exceeded the ` +
|
|
45
|
-
`${CONFIG_EVALUATOR_MAX_BUFFER / (1024 * 1024)} MiB output limit. ` +
|
|
46
|
-
"Reduce console output from the config and its dependencies.",
|
|
47
|
-
);
|
|
48
|
-
}
|
|
49
29
|
if (result.error) {
|
|
50
30
|
return new Error(
|
|
51
31
|
`@ttsc/lint: failed to spawn ttsx for ${configPath}: ${result.error.message}`,
|
|
@@ -57,43 +37,34 @@ export function configEvaluatorProcessFailure(
|
|
|
57
37
|
);
|
|
58
38
|
}
|
|
59
39
|
if (result.status !== 0) {
|
|
60
|
-
const reason = configEvaluatorFailureReason(result.stderr);
|
|
61
40
|
return new Error(
|
|
62
|
-
`@ttsc/lint: lint config ${configPath} evaluation failed with exit code ${String(result.status)}
|
|
63
|
-
(reason === "" ? "" : "\n" + reason),
|
|
41
|
+
`@ttsc/lint: lint config ${configPath} evaluation failed with exit code ${String(result.status)}`,
|
|
64
42
|
);
|
|
65
43
|
}
|
|
66
44
|
return undefined;
|
|
67
45
|
}
|
|
68
46
|
|
|
69
47
|
/**
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Return the useful tail of evaluator stderr without turning an exception into
|
|
85
|
-
* an unbounded duplicate of the already-forwarded child stream.
|
|
48
|
+
* Read the failure envelope the evaluator writes to its result file when it
|
|
49
|
+
* stops on an error it can name.
|
|
50
|
+
*
|
|
51
|
+
* This is the other half of classifying how the evaluation ended, which is why
|
|
52
|
+
* it lives beside {@link configEvaluatorProcessFailure} rather than at the call
|
|
53
|
+
* site: the status says that it failed, and this says why.
|
|
54
|
+
*
|
|
55
|
+
* Only a well-formed envelope is honoured. A real evaluation payload never
|
|
56
|
+
* carries this key, and every other shape — an absent file, a build that failed
|
|
57
|
+
* before the loader ran, a half-written result, a payload written before a
|
|
58
|
+
* later non-zero exit — leaves the process status to speak for itself.
|
|
86
59
|
*/
|
|
87
|
-
function configEvaluatorFailureReason(
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
.
|
|
94
|
-
|
|
95
|
-
|
|
60
|
+
export function configEvaluatorFailureReason(outputPath: string): string {
|
|
61
|
+
try {
|
|
62
|
+
const parsed: unknown = JSON.parse(fs.readFileSync(outputPath, "utf8"));
|
|
63
|
+
if (typeof parsed !== "object" || parsed === null) return "";
|
|
64
|
+
const message = (parsed as { __ttscLoaderError?: unknown })
|
|
65
|
+
.__ttscLoaderError;
|
|
66
|
+
return typeof message === "string" ? message.trim() : "";
|
|
67
|
+
} catch {
|
|
68
|
+
return "";
|
|
69
|
+
}
|
|
96
70
|
}
|
|
97
|
-
|
|
98
|
-
const CONFIG_EVALUATOR_REASON_LINES = 5;
|
|
99
|
-
const CONFIG_EVALUATOR_REASON_MAX_CHARS = 8 * 1024;
|
|
@@ -80,24 +80,45 @@ export interface ITtscLintFunctionalNoThrowStatementsRuleOptions {
|
|
|
80
80
|
|
|
81
81
|
/** `functional/no-mixed-types` rule options. */
|
|
82
82
|
export interface ITtscLintFunctionalNoMixedTypesRuleOptions {
|
|
83
|
-
/**
|
|
83
|
+
/**
|
|
84
|
+
* Check interface member kinds.
|
|
85
|
+
*
|
|
86
|
+
* @default true
|
|
87
|
+
*/
|
|
84
88
|
checkInterfaces?: boolean;
|
|
85
89
|
|
|
86
|
-
/**
|
|
90
|
+
/**
|
|
91
|
+
* Check type-literal member kinds.
|
|
92
|
+
*
|
|
93
|
+
* @default true
|
|
94
|
+
*/
|
|
87
95
|
checkTypeLiterals?: boolean;
|
|
88
96
|
}
|
|
89
97
|
|
|
90
98
|
/** `functional/no-return-void` rule options. */
|
|
91
99
|
export interface ITtscLintFunctionalNoReturnVoidRuleOptions {
|
|
92
|
-
/**
|
|
100
|
+
/**
|
|
101
|
+
* Permit a function whose declared return type is `null`. Set `false` to
|
|
102
|
+
* reject it the way a declared `void` is rejected.
|
|
103
|
+
*
|
|
104
|
+
* @default true
|
|
105
|
+
*/
|
|
93
106
|
allowNull?: boolean;
|
|
94
107
|
|
|
95
|
-
/**
|
|
108
|
+
/**
|
|
109
|
+
* Permit a function whose declared return type is `undefined`. Set `false` to
|
|
110
|
+
* reject it the way a declared `void` is rejected.
|
|
111
|
+
*
|
|
112
|
+
* @default true
|
|
113
|
+
*/
|
|
96
114
|
allowUndefined?: boolean;
|
|
97
115
|
|
|
98
116
|
/**
|
|
99
|
-
* Skip
|
|
100
|
-
*
|
|
117
|
+
* Skip a bare `return;` inside a function that declares no return type. That
|
|
118
|
+
* statement is the one place the rule rejects a void-ness it inferred rather
|
|
119
|
+
* than read from an annotation.
|
|
120
|
+
*
|
|
121
|
+
* @default false
|
|
101
122
|
*/
|
|
102
123
|
ignoreInferredTypes?: boolean;
|
|
103
124
|
}
|
|
@@ -105,7 +126,8 @@ export interface ITtscLintFunctionalNoReturnVoidRuleOptions {
|
|
|
105
126
|
/** `functional/prefer-immutable-types` rule options. */
|
|
106
127
|
export interface ITtscLintFunctionalPreferImmutableTypesRuleOptions extends ITtscLintFunctionalPatternOptions {
|
|
107
128
|
/**
|
|
108
|
-
* Minimum accepted immutability.
|
|
129
|
+
* Minimum accepted immutability. Reserved for upstream-compatible configs;
|
|
130
|
+
* the native subset computes no immutability level and treats any configured
|
|
109
131
|
* value as readonly-required.
|
|
110
132
|
*/
|
|
111
133
|
enforcement?:
|
|
@@ -118,31 +140,62 @@ export interface ITtscLintFunctionalPreferImmutableTypesRuleOptions extends ITts
|
|
|
118
140
|
|
|
119
141
|
/** `functional/prefer-readonly-type` rule options. */
|
|
120
142
|
export interface ITtscLintFunctionalPreferReadonlyTypeRuleOptions extends ITtscLintFunctionalPatternOptions {
|
|
121
|
-
/**
|
|
143
|
+
/**
|
|
144
|
+
* Permit mutation of locals while still policing exported types. Reserved for
|
|
145
|
+
* upstream-compatible configs; the native subset reads type annotations and
|
|
146
|
+
* models no local-versus-exported distinction.
|
|
147
|
+
*/
|
|
122
148
|
allowLocalMutation?: boolean;
|
|
123
149
|
|
|
124
|
-
/**
|
|
150
|
+
/**
|
|
151
|
+
* Permit a mutable return type even when parameters must be readonly. Covers
|
|
152
|
+
* every signature that can declare one, including call and construct
|
|
153
|
+
* signatures, constructor types, and a get accessor.
|
|
154
|
+
*
|
|
155
|
+
* @default false
|
|
156
|
+
*/
|
|
125
157
|
allowMutableReturnType?: boolean;
|
|
126
158
|
|
|
127
|
-
/**
|
|
159
|
+
/**
|
|
160
|
+
* Also check property positions that have no explicit type annotation.
|
|
161
|
+
* Reserved for upstream-compatible configs; judging an unannotated position
|
|
162
|
+
* needs the type checker, which this rule does not use.
|
|
163
|
+
*/
|
|
128
164
|
checkImplicit?: boolean;
|
|
129
165
|
|
|
130
|
-
/**
|
|
166
|
+
/**
|
|
167
|
+
* Skip array / tuple / `Map` / `Set` types.
|
|
168
|
+
*
|
|
169
|
+
* @default false
|
|
170
|
+
*/
|
|
131
171
|
ignoreCollections?: boolean;
|
|
132
172
|
|
|
133
173
|
/**
|
|
134
|
-
* Skip class
|
|
135
|
-
*
|
|
174
|
+
* Skip class members. `true` skips anything under a class, its heritage
|
|
175
|
+
* clause and type parameters included; `"fieldsOnly"` narrows that to field
|
|
176
|
+
* declarations and keeps methods, accessors, and constructor parameters
|
|
177
|
+
* checked.
|
|
178
|
+
*
|
|
179
|
+
* @default false
|
|
136
180
|
*/
|
|
137
181
|
ignoreClass?: boolean | "fieldsOnly";
|
|
138
182
|
|
|
139
|
-
/**
|
|
183
|
+
/**
|
|
184
|
+
* Skip interface members entirely.
|
|
185
|
+
*
|
|
186
|
+
* @default false
|
|
187
|
+
*/
|
|
140
188
|
ignoreInterface?: boolean;
|
|
141
189
|
}
|
|
142
190
|
|
|
143
191
|
/** `functional/prefer-tacit` rule options. */
|
|
144
192
|
export interface ITtscLintFunctionalPreferTacitRuleOptions {
|
|
145
|
-
/**
|
|
193
|
+
/**
|
|
194
|
+
* Check member expressions such as `x => service.map(x)`. Set `false` to keep
|
|
195
|
+
* the rule on bare-identifier callees only.
|
|
196
|
+
*
|
|
197
|
+
* @default true
|
|
198
|
+
*/
|
|
146
199
|
checkMemberExpressions?: boolean;
|
|
147
200
|
}
|
|
148
201
|
|
|
@@ -167,7 +220,11 @@ export interface ITtscLintFunctionalTypeDeclarationImmutabilityRule {
|
|
|
167
220
|
*/
|
|
168
221
|
immutability?: "ReadonlyShallow" | "ReadonlyDeep" | "Immutable" | "Mutable";
|
|
169
222
|
|
|
170
|
-
/**
|
|
223
|
+
/**
|
|
224
|
+
* Comparator applied to the immutability level above. Reserved for
|
|
225
|
+
* upstream-compatible configs alongside `immutability`: the native subset
|
|
226
|
+
* computes no immutability level, so there is nothing to compare.
|
|
227
|
+
*/
|
|
171
228
|
comparator?:
|
|
172
229
|
| "Less"
|
|
173
230
|
| "AtMost"
|