@ttsc/lint 0.8.0-dev.20260506 → 0.8.1
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 +72 -2
- package/lib/config.d.ts +3 -0
- package/lib/config.js +3 -0
- package/lib/config.js.map +1 -0
- package/lib/structures/ITtscLintPluginConfig.d.ts +3 -2
- package/lib/structures/TtscLintConfig.d.ts +2 -3
- package/package.json +8 -5
- package/plugin/compile.go +27 -10
- package/plugin/config.go +760 -29
- package/plugin/directives.go +296 -0
- package/plugin/engine.go +29 -9
- package/plugin/eslint_runtime.go +246 -0
- package/src/config.ts +3 -0
- package/src/structures/ITtscLintPluginConfig.ts +3 -2
- package/src/structures/TtscLintConfig.ts +2 -3
- package/tsconfig.json +0 -10
package/README.md
CHANGED
|
@@ -112,20 +112,88 @@ You can also keep the rules in a standalone file and reference it from `tsconfig
|
|
|
112
112
|
|
|
113
113
|
```ts
|
|
114
114
|
// ttsc-lint.config.ts
|
|
115
|
+
import type { TtscLintConfig } from "@ttsc/lint/config";
|
|
116
|
+
|
|
115
117
|
export default {
|
|
116
118
|
"no-var": "error",
|
|
117
119
|
"prefer-const": "error",
|
|
118
120
|
"no-explicit-any": "warning",
|
|
119
121
|
"no-console": "off",
|
|
120
|
-
};
|
|
122
|
+
} satisfies TtscLintConfig;
|
|
121
123
|
```
|
|
122
124
|
|
|
123
125
|
The `config` field accepts:
|
|
124
126
|
|
|
125
127
|
- An inline object (shown earlier), **or** a path to a file ending in `.json`, `.js`, `.cjs`, `.mjs`, `.ts`, `.cts`, or `.mts`.
|
|
126
128
|
- For JS/TS configs: a `default` export, a named `config` export, a direct module export, or a function that returns the object.
|
|
129
|
+
- If `config` is omitted, `@ttsc/lint` discovers the nearest `eslint.config.js`, `eslint.config.mjs`, `eslint.config.cjs`, `eslint.config.ts`, `eslint.config.mts`, or `eslint.config.cts` from the owning `tsconfig.json` directory upward.
|
|
130
|
+
- Inline objects use the standard `@ttsc/lint` native rule map shape.
|
|
131
|
+
- Standalone config files may additionally export ESLint flat-config-style objects or arrays. `@ttsc/lint` lowers their `basePath`, `files`, `ignores`, object/array `extends`, and `rules` fields into native per-file rule maps, accepts ESLint severity tuples, and maps `@typescript-eslint/<rule>` keys to native rule names when a matching native rule exists.
|
|
127
132
|
- Relative paths resolve from the directory of the owning `tsconfig.json`.
|
|
128
133
|
|
|
134
|
+
For example, a flat-config-style file can enable native `@ttsc/lint` rules with
|
|
135
|
+
ESLint or `@typescript-eslint/*` keys:
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
// eslint.config.ts
|
|
139
|
+
import type { Linter } from "eslint";
|
|
140
|
+
|
|
141
|
+
const config = [
|
|
142
|
+
{
|
|
143
|
+
rules: {
|
|
144
|
+
"@typescript-eslint/no-explicit-any": "error",
|
|
145
|
+
"@typescript-eslint/consistent-type-imports": [
|
|
146
|
+
"warning",
|
|
147
|
+
{ fixStyle: "inline-type-imports" },
|
|
148
|
+
],
|
|
149
|
+
"no-console": "off",
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
] satisfies Linter.Config[];
|
|
153
|
+
|
|
154
|
+
export default config;
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Config factories can be used when they return a resolved flat config object or
|
|
158
|
+
array. Current `typescript-eslint` versions prefer ESLint's `defineConfig(...)`;
|
|
159
|
+
existing configs that still use `typescript-eslint`'s `config(...)` are readable
|
|
160
|
+
when they export the resolved object/array shape:
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
// eslint.config.ts
|
|
164
|
+
import tseslint, { type ConfigArray } from "typescript-eslint";
|
|
165
|
+
|
|
166
|
+
const config = tseslint.config({
|
|
167
|
+
extends: [tseslint.configs.recommended],
|
|
168
|
+
rules: {
|
|
169
|
+
"@typescript-eslint/no-explicit-any": "error",
|
|
170
|
+
},
|
|
171
|
+
}) satisfies ConfigArray;
|
|
172
|
+
|
|
173
|
+
export default config;
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
When a project has `eslint` installed and the external config is an
|
|
177
|
+
`eslint.config.*` file, or the config uses runtime-only fields such as `plugins`,
|
|
178
|
+
`languageOptions`, `processor`, `settings`, or string `extends`, `@ttsc/lint`
|
|
179
|
+
delegates diagnostics to the installed ESLint runtime and forwards its rule ids,
|
|
180
|
+
severities, messages, and locations. This is the path that runs external
|
|
181
|
+
RuleModules such as `@typescript-eslint/no-floating-promises`; typed
|
|
182
|
+
typescript-eslint rules work when your ESLint config supplies the usual parser
|
|
183
|
+
and `parserOptions.project` / `projectService` settings.
|
|
184
|
+
|
|
185
|
+
When ESLint is not installed and the config can be represented as native rules,
|
|
186
|
+
`@ttsc/lint` falls back to the lowered native rule map. That fallback supports
|
|
187
|
+
`files`, `ignores`, `basePath`, object/array `extends`, `rules`, severity tuples,
|
|
188
|
+
and `@typescript-eslint/` or `typescript-eslint/` rule prefixes. Runtime-only
|
|
189
|
+
features such as `plugins`, `languageOptions`, `processor`, `settings`,
|
|
190
|
+
`linterOptions`, or string `extends` require ESLint to be installed.
|
|
191
|
+
|
|
192
|
+
Inline disable comments are respected in both paths. Use the ESLint forms
|
|
193
|
+
`eslint-disable-next-line`, `eslint-disable-line`, `eslint-disable`, and
|
|
194
|
+
`eslint-enable`; the native engine also accepts the `lint-*` aliases for the
|
|
195
|
+
same directives.
|
|
196
|
+
|
|
129
197
|
## Plugin order
|
|
130
198
|
|
|
131
199
|
`@ttsc/lint` must be the first plugin in `compilerOptions.plugins`.
|
|
@@ -153,7 +221,9 @@ It inspects the source you wrote, so transform plugins such as `@ttsc/banner`, `
|
|
|
153
221
|
|
|
154
222
|
## Scope
|
|
155
223
|
|
|
156
|
-
Diagnostic-only today: no autofix
|
|
224
|
+
Diagnostic-only today: no autofix and no bundled recommended preset. The native
|
|
225
|
+
engine does not load custom Go rules; external ESLint RuleModules run through
|
|
226
|
+
the installed ESLint runtime path described above.
|
|
157
227
|
|
|
158
228
|
## Rules
|
|
159
229
|
|
package/lib/config.d.ts
ADDED
package/lib/config.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":""}
|
|
@@ -3,10 +3,11 @@ import type { TtscLintConfig } from "./TtscLintConfig";
|
|
|
3
3
|
/** `compilerOptions.plugins[]` entry shape consumed by `@ttsc/lint`. */
|
|
4
4
|
export interface ITtscLintPluginConfig extends ITtscProjectPluginConfig {
|
|
5
5
|
/**
|
|
6
|
-
* Inline rule map or path to a
|
|
6
|
+
* Inline rule map or path to a standalone lint config file.
|
|
7
7
|
*
|
|
8
8
|
* Inline maps are passed to the native sidecar through `--plugins-json`.
|
|
9
|
-
* String values are resolved by the sidecar from the
|
|
9
|
+
* String values are resolved by the sidecar from the owning tsconfig
|
|
10
|
+
* directory and may point at JSON, JavaScript, or TypeScript config files.
|
|
10
11
|
*/
|
|
11
12
|
config?: string | TtscLintConfig;
|
|
12
13
|
}
|
|
@@ -3,9 +3,8 @@ import type { TtscLintSeverity } from "./TtscLintSeverity";
|
|
|
3
3
|
/**
|
|
4
4
|
* Inline rule map accepted by the `@ttsc/lint` tsconfig plugin entry.
|
|
5
5
|
*
|
|
6
|
-
* Each property key is a native lint rule name. Omitted rules
|
|
7
|
-
*
|
|
8
|
-
* project.
|
|
6
|
+
* Each property key is a native lint rule name. Omitted rules are disabled,
|
|
7
|
+
* while present rules enable or disable that rule for the current project.
|
|
9
8
|
*/
|
|
10
9
|
export type TtscLintConfig = {
|
|
11
10
|
[P in TtscLintRule]?: TtscLintSeverity;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ttsc/lint",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"description": "Reference ttsc plugin: ESLint-style lint rules hosted in the same Program/Checker as the type-check pass.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
@@ -9,6 +9,10 @@
|
|
|
9
9
|
"types": "./lib/index.d.ts",
|
|
10
10
|
"default": "./lib/index.js"
|
|
11
11
|
},
|
|
12
|
+
"./config": {
|
|
13
|
+
"types": "./lib/config.d.ts",
|
|
14
|
+
"default": "./lib/config.js"
|
|
15
|
+
},
|
|
12
16
|
"./package.json": "./package.json"
|
|
13
17
|
},
|
|
14
18
|
"keywords": [
|
|
@@ -22,15 +26,14 @@
|
|
|
22
26
|
"README.md",
|
|
23
27
|
"lib",
|
|
24
28
|
"src",
|
|
25
|
-
"tsconfig.json",
|
|
26
29
|
"go.mod",
|
|
27
30
|
"plugin"
|
|
28
31
|
],
|
|
29
32
|
"devDependencies": {
|
|
30
|
-
"@typescript/native-preview": "7.0.0-dev.
|
|
33
|
+
"@typescript/native-preview": "7.0.0-dev.20260506.1",
|
|
31
34
|
"@types/node": "^25.3.0",
|
|
32
35
|
"rimraf": "^6.1.2",
|
|
33
|
-
"ttsc": "0.8.
|
|
36
|
+
"ttsc": "0.8.1"
|
|
34
37
|
},
|
|
35
38
|
"repository": {
|
|
36
39
|
"type": "git",
|
|
@@ -43,7 +46,7 @@
|
|
|
43
46
|
},
|
|
44
47
|
"homepage": "https://github.com/samchon/ttsc/tree/master/packages/lint#readme",
|
|
45
48
|
"scripts": {
|
|
46
|
-
"build": "rimraf lib && tsgo
|
|
49
|
+
"build": "rimraf lib && tsgo",
|
|
47
50
|
"test": "pnpm run build && pnpm --dir ../../tests/lint start"
|
|
48
51
|
}
|
|
49
52
|
}
|
package/plugin/compile.go
CHANGED
|
@@ -95,10 +95,16 @@ func RunTransform(args []string) int {
|
|
|
95
95
|
fmt.Fprintln(os.Stderr, err)
|
|
96
96
|
return 2
|
|
97
97
|
}
|
|
98
|
-
engine :=
|
|
99
|
-
warnUnknownRules(os.Stderr, engine.UnknownRules())
|
|
98
|
+
engine := NewEngineWithResolver(rules)
|
|
100
99
|
|
|
101
|
-
astDiags, lintDiags := collectDiagnostics(prog, engine)
|
|
100
|
+
astDiags, lintDiags, externalRan, err := collectDiagnostics(prog, engine)
|
|
101
|
+
if err != nil {
|
|
102
|
+
fmt.Fprintln(os.Stderr, err)
|
|
103
|
+
return 2
|
|
104
|
+
}
|
|
105
|
+
if !externalRan {
|
|
106
|
+
warnUnknownRules(os.Stderr, engine.UnknownRules())
|
|
107
|
+
}
|
|
102
108
|
if errors := shimdw.FormatMixedDiagnostics(os.Stderr, astDiags, lintDiags, resolvedCwd); errors > 0 {
|
|
103
109
|
return 2
|
|
104
110
|
}
|
|
@@ -225,10 +231,16 @@ func runProject(opts *subcommandOpts) int {
|
|
|
225
231
|
fmt.Fprintln(os.Stderr, err)
|
|
226
232
|
return 2
|
|
227
233
|
}
|
|
228
|
-
engine :=
|
|
229
|
-
warnUnknownRules(os.Stderr, engine.UnknownRules())
|
|
234
|
+
engine := NewEngineWithResolver(rules)
|
|
230
235
|
|
|
231
|
-
astDiags, lintDiags := collectDiagnostics(prog, engine)
|
|
236
|
+
astDiags, lintDiags, externalRan, err := collectDiagnostics(prog, engine)
|
|
237
|
+
if err != nil {
|
|
238
|
+
fmt.Fprintln(os.Stderr, err)
|
|
239
|
+
return 2
|
|
240
|
+
}
|
|
241
|
+
if !externalRan {
|
|
242
|
+
warnUnknownRules(os.Stderr, engine.UnknownRules())
|
|
243
|
+
}
|
|
232
244
|
if errCount := shimdw.FormatMixedDiagnostics(os.Stderr, astDiags, lintDiags, opts.cwd); errCount > 0 {
|
|
233
245
|
return 2
|
|
234
246
|
}
|
|
@@ -261,7 +273,7 @@ func runProject(opts *subcommandOpts) int {
|
|
|
261
273
|
return 0
|
|
262
274
|
}
|
|
263
275
|
|
|
264
|
-
func loadRules(pluginsJSON, cwd, tsconfigPath string) (
|
|
276
|
+
func loadRules(pluginsJSON, cwd, tsconfigPath string) (RuleResolver, error) {
|
|
265
277
|
entries, err := ParsePlugins(pluginsJSON)
|
|
266
278
|
if err != nil {
|
|
267
279
|
return nil, err
|
|
@@ -273,7 +285,7 @@ func loadRules(pluginsJSON, cwd, tsconfigPath string) (RuleConfig, error) {
|
|
|
273
285
|
if entry == nil {
|
|
274
286
|
return RuleConfig{}, nil
|
|
275
287
|
}
|
|
276
|
-
return
|
|
288
|
+
return LoadConfigResolver(entry, cwd, tsconfigPath)
|
|
277
289
|
}
|
|
278
290
|
|
|
279
291
|
func warnUnknownRules(w io.Writer, unknown []string) {
|
|
@@ -314,9 +326,14 @@ func filterKnownFlags(args []string, known map[string]bool) []string {
|
|
|
314
326
|
// collectDiagnostics merges tsgo typecheck diagnostics with the lint
|
|
315
327
|
// engine's findings. The renderer takes the two slices and walks them in
|
|
316
328
|
// source order, so we don't need to interleave here.
|
|
317
|
-
func collectDiagnostics(prog *program, engine *Engine) ([]*shimast.Diagnostic, []*shimdw.LintDiagnostic) {
|
|
329
|
+
func collectDiagnostics(prog *program, engine *Engine) ([]*shimast.Diagnostic, []*shimdw.LintDiagnostic, bool, error) {
|
|
318
330
|
astDiags := prog.programDiagnostics()
|
|
319
331
|
files := prog.userSourceFiles()
|
|
332
|
+
if externalDiags, ran, err := runExternalESLintDiagnostics(engine.config, prog.cwd, files); err != nil {
|
|
333
|
+
return nil, nil, false, err
|
|
334
|
+
} else if ran {
|
|
335
|
+
return astDiags, externalDiags, true, nil
|
|
336
|
+
}
|
|
320
337
|
findings := engine.Run(files, prog.checker)
|
|
321
338
|
lintDiags := make([]*shimdw.LintDiagnostic, 0, len(findings))
|
|
322
339
|
for _, finding := range findings {
|
|
@@ -333,7 +350,7 @@ func collectDiagnostics(prog *program, engine *Engine) ([]*shimast.Diagnostic, [
|
|
|
333
350
|
fmt.Sprintf("[%s] %s", finding.Rule, finding.Message),
|
|
334
351
|
))
|
|
335
352
|
}
|
|
336
|
-
return astDiags, lintDiags
|
|
353
|
+
return astDiags, lintDiags, false, nil
|
|
337
354
|
}
|
|
338
355
|
|
|
339
356
|
// RuleCode hashes a rule name into a stable, positive int32 so the
|