@ttsc/lint 0.11.0-dev.20260518-2 → 0.12.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 +67 -141
- package/lib/{structures/defaultFormat.d.ts → defaultFormat.d.ts} +3 -3
- package/lib/{structures/defaultFormat.js → defaultFormat.js} +3 -3
- package/lib/defaultFormat.js.map +1 -0
- package/lib/index.d.ts +1 -38
- package/lib/index.js +7 -5
- package/lib/index.js.map +1 -1
- package/lib/structures/ITtscLintConfig.d.ts +27 -0
- package/lib/structures/{PluginRuleNames.js → ITtscLintConfig.js} +1 -1
- package/lib/structures/ITtscLintConfig.js.map +1 -0
- package/lib/structures/{TtscLintFormatConfig.d.ts → ITtscLintFormatConfig.d.ts} +17 -16
- package/lib/structures/{TtscLintConfig.js → ITtscLintFormatConfig.js} +1 -1
- package/lib/structures/ITtscLintFormatConfig.js.map +1 -0
- package/lib/structures/ITtscLintPluginConfig.d.ts +6 -6
- package/lib/structures/TtscLintRuleMap.d.ts +11 -9
- package/lib/structures/TtscLintRuleOptions.d.ts +118 -144
- package/lib/structures/index.d.ts +2 -7
- package/lib/structures/index.js +2 -7
- package/lib/structures/index.js.map +1 -1
- package/linthost/config.go +1 -6
- package/linthost/config_format.go +253 -298
- package/linthost/format.go +91 -1
- package/package.json +3 -3
- package/rule/rule.go +124 -124
- package/src/{structures/defaultFormat.ts → defaultFormat.ts} +5 -5
- package/src/index.ts +7 -5
- package/src/structures/ITtscLintConfig.ts +33 -0
- package/src/structures/{TtscLintFormatConfig.ts → ITtscLintFormatConfig.ts} +17 -16
- package/src/structures/ITtscLintPluginConfig.ts +6 -6
- package/src/structures/TtscLintRuleMap.ts +17 -13
- package/src/structures/TtscLintRuleOptions.ts +118 -144
- package/src/structures/index.ts +2 -7
- package/lib/defineConfig.d.ts +0 -55
- package/lib/defineConfig.js +0 -39
- package/lib/defineConfig.js.map +0 -1
- package/lib/structures/PluginRuleNames.d.ts +0 -14
- package/lib/structures/PluginRuleNames.js.map +0 -1
- package/lib/structures/TtscLintConfig.d.ts +0 -27
- package/lib/structures/TtscLintConfig.js.map +0 -1
- package/lib/structures/TtscLintConfigEntry.d.ts +0 -39
- package/lib/structures/TtscLintConfigEntry.js +0 -3
- package/lib/structures/TtscLintConfigEntry.js.map +0 -1
- package/lib/structures/TtscLintFormatConfig.js +0 -3
- package/lib/structures/TtscLintFormatConfig.js.map +0 -1
- package/lib/structures/TtscLintPlugins.d.ts +0 -9
- package/lib/structures/TtscLintPlugins.js +0 -3
- package/lib/structures/TtscLintPlugins.js.map +0 -1
- package/lib/structures/TtscLintRuleEntry.d.ts +0 -22
- package/lib/structures/TtscLintRuleEntry.js +0 -3
- package/lib/structures/TtscLintRuleEntry.js.map +0 -1
- package/lib/structures/defaultFormat.js.map +0 -1
- package/src/defineConfig.ts +0 -69
- package/src/structures/PluginRuleNames.ts +0 -19
- package/src/structures/TtscLintConfig.ts +0 -31
- package/src/structures/TtscLintConfigEntry.ts +0 -50
- package/src/structures/TtscLintPlugins.ts +0 -10
- package/src/structures/TtscLintRuleEntry.ts +0 -28
package/linthost/format.go
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
package linthost
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
|
+
"encoding/json"
|
|
4
5
|
"fmt"
|
|
5
6
|
"os"
|
|
7
|
+
"sort"
|
|
6
8
|
)
|
|
7
9
|
|
|
8
10
|
// maxFormatPasses bounds the format cascade for the same reason
|
|
@@ -51,7 +53,7 @@ func runFormat(opts *subcommandOpts) int {
|
|
|
51
53
|
totalFixes := 0
|
|
52
54
|
cascadeConverged := false
|
|
53
55
|
for pass := 0; pass < maxFormatPasses; pass++ {
|
|
54
|
-
engine := NewEngineWithResolver(rules)
|
|
56
|
+
engine := NewEngineWithResolver(formatCommandResolver{inner: rules})
|
|
55
57
|
findings := engine.Run(prog.userSourceFiles(), prog.checker)
|
|
56
58
|
fixed, err := applyFindingFixes(opts.cwd, filterFormatFindings(findings))
|
|
57
59
|
if err != nil {
|
|
@@ -87,6 +89,94 @@ func runFormat(opts *subcommandOpts) int {
|
|
|
87
89
|
return 0
|
|
88
90
|
}
|
|
89
91
|
|
|
92
|
+
type formatCommandResolver struct {
|
|
93
|
+
inner RuleResolver
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
func (r formatCommandResolver) ResolveRules(fileName string) ResolvedRuleConfig {
|
|
97
|
+
resolved := r.inner.ResolveRules(fileName)
|
|
98
|
+
if resolved.Ignored {
|
|
99
|
+
return resolved
|
|
100
|
+
}
|
|
101
|
+
if resolved.Rules == nil {
|
|
102
|
+
resolved.Rules = RuleConfig{}
|
|
103
|
+
}
|
|
104
|
+
for _, name := range r.formatOptionRuleNames() {
|
|
105
|
+
if resolved.Rules.Severity(name) == SeverityOff {
|
|
106
|
+
resolved.Rules[name] = SeverityWarn
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return resolved
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
func (r formatCommandResolver) ActiveRuleNames() []string {
|
|
113
|
+
active := map[string]struct{}{}
|
|
114
|
+
for _, name := range r.inner.ActiveRuleNames() {
|
|
115
|
+
active[name] = struct{}{}
|
|
116
|
+
}
|
|
117
|
+
for _, name := range r.formatOptionRuleNames() {
|
|
118
|
+
active[name] = struct{}{}
|
|
119
|
+
}
|
|
120
|
+
return sortedKeys(active)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
func (r formatCommandResolver) EnabledRuleConfig() RuleConfig {
|
|
124
|
+
enabled := r.inner.EnabledRuleConfig()
|
|
125
|
+
if enabled == nil {
|
|
126
|
+
enabled = RuleConfig{}
|
|
127
|
+
}
|
|
128
|
+
for _, name := range r.formatOptionRuleNames() {
|
|
129
|
+
if enabled.Severity(name) == SeverityOff {
|
|
130
|
+
enabled[name] = SeverityWarn
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return enabled
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
func (r formatCommandResolver) RuleOptions(name string) json.RawMessage {
|
|
137
|
+
return r.inner.RuleOptions(name)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
func (r formatCommandResolver) formatOptionRuleNames() []string {
|
|
141
|
+
options := resolverOptions(r.inner)
|
|
142
|
+
if len(options) == 0 {
|
|
143
|
+
return nil
|
|
144
|
+
}
|
|
145
|
+
names := make([]string, 0, len(options))
|
|
146
|
+
for name := range options {
|
|
147
|
+
if isRegisteredFormatRule(name) {
|
|
148
|
+
names = append(names, name)
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
sort.Strings(names)
|
|
152
|
+
return names
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
func resolverOptions(resolver RuleResolver) RuleOptionsMap {
|
|
156
|
+
switch r := resolver.(type) {
|
|
157
|
+
case InlineRuleResolver:
|
|
158
|
+
return r.Options
|
|
159
|
+
case *ConfigStore:
|
|
160
|
+
return r.options
|
|
161
|
+
default:
|
|
162
|
+
return nil
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
func isRegisteredFormatRule(name string) bool {
|
|
167
|
+
rule, ok := registered.rules[name]
|
|
168
|
+
return ok && isFormatRule(rule)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
func sortedKeys(input map[string]struct{}) []string {
|
|
172
|
+
names := make([]string, 0, len(input))
|
|
173
|
+
for name := range input {
|
|
174
|
+
names = append(names, name)
|
|
175
|
+
}
|
|
176
|
+
sort.Strings(names)
|
|
177
|
+
return names
|
|
178
|
+
}
|
|
179
|
+
|
|
90
180
|
// filterFormatFindings keeps only findings produced by FormatRule
|
|
91
181
|
// implementations that also carry at least one autofix edit.
|
|
92
182
|
// `RunFormat` calls this so the format-only subcommand never applies
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ttsc/lint",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
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",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"@typescript/native-preview": "7.0.0-dev.20260515.1",
|
|
37
37
|
"@types/node": "^25.3.0",
|
|
38
38
|
"rimraf": "^6.1.2",
|
|
39
|
-
"ttsc": "0.
|
|
39
|
+
"ttsc": "0.12.0"
|
|
40
40
|
},
|
|
41
41
|
"repository": {
|
|
42
42
|
"type": "git",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"bugs": {
|
|
48
48
|
"url": "https://github.com/samchon/ttsc/issues"
|
|
49
49
|
},
|
|
50
|
-
"homepage": "https://
|
|
50
|
+
"homepage": "https://ttsc.dev",
|
|
51
51
|
"scripts": {
|
|
52
52
|
"build": "rimraf lib && tsgo"
|
|
53
53
|
}
|
package/rule/rule.go
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// adapts each contributor rule onto the same dispatch table that drives
|
|
11
11
|
// the built-in rules.
|
|
12
12
|
//
|
|
13
|
-
// Contributors operate on the same shim AST the host
|
|
13
|
+
// Contributors operate on the same shim AST the host and linked transform
|
|
14
14
|
// plugins use (`github.com/microsoft/typescript-go/shim/ast` and friends)
|
|
15
15
|
// — there is no facade layer in between. The shim packages are the
|
|
16
16
|
// publicly maintained boundary ttsc already exposes; adding another
|
|
@@ -21,30 +21,30 @@
|
|
|
21
21
|
//
|
|
22
22
|
// Example contributor:
|
|
23
23
|
//
|
|
24
|
-
//
|
|
24
|
+
// package myrules
|
|
25
25
|
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
//
|
|
26
|
+
// import (
|
|
27
|
+
// shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
28
|
+
// "github.com/samchon/ttsc/packages/lint/rule"
|
|
29
|
+
// )
|
|
30
30
|
//
|
|
31
|
-
//
|
|
31
|
+
// func init() { rule.Register(noTodoComment{}) }
|
|
32
32
|
//
|
|
33
|
-
//
|
|
33
|
+
// type noTodoComment struct{}
|
|
34
34
|
//
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
//
|
|
35
|
+
// func (noTodoComment) Name() string { return "demo/no-todo-comment" }
|
|
36
|
+
// func (noTodoComment) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindSourceFile} }
|
|
37
|
+
// func (noTodoComment) Check(ctx *rule.Context, node *shimast.Node) {
|
|
38
|
+
// // ctx.File, ctx.Checker, ctx.Severity available; ctx.Report(node, msg)
|
|
39
|
+
// // or ctx.ReportRange(pos, end, msg) push a finding through the engine.
|
|
40
|
+
// }
|
|
41
41
|
package rule
|
|
42
42
|
|
|
43
43
|
import (
|
|
44
|
-
|
|
44
|
+
"encoding/json"
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
47
|
+
shimchecker "github.com/microsoft/typescript-go/shim/checker"
|
|
48
48
|
)
|
|
49
49
|
|
|
50
50
|
// Severity mirrors the engine's three-level severity ladder. The
|
|
@@ -53,31 +53,31 @@ import (
|
|
|
53
53
|
type Severity int
|
|
54
54
|
|
|
55
55
|
const (
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
56
|
+
// SeverityOff means the rule is disabled. Engine skips dispatch.
|
|
57
|
+
SeverityOff Severity = iota
|
|
58
|
+
// SeverityWarn produces a warning diagnostic (does not change exit
|
|
59
|
+
// code).
|
|
60
|
+
SeverityWarn
|
|
61
|
+
// SeverityError produces an error diagnostic and fails the command.
|
|
62
|
+
SeverityError
|
|
63
63
|
)
|
|
64
64
|
|
|
65
65
|
// Rule is the contract every contributor rule satisfies. Mirrors the
|
|
66
66
|
// internal host interface so the host can dispatch via a thin adapter
|
|
67
67
|
// without re-implementing the engine.
|
|
68
68
|
type Rule interface {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
69
|
+
// Name is the identifier users put in their `rules` map.
|
|
70
|
+
// Conventionally namespaced as "<plugin-namespace>/<rule-name>" to
|
|
71
|
+
// avoid colliding with built-in rule names.
|
|
72
|
+
Name() string
|
|
73
73
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
74
|
+
// Visits returns the AST kinds the rule cares about. The engine only
|
|
75
|
+
// dispatches to rules that registered for the visited node's kind.
|
|
76
|
+
Visits() []shimast.Kind
|
|
77
77
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
// Check is invoked once per relevant node. Use `ctx.Report` /
|
|
79
|
+
// `ctx.ReportRange` to emit findings.
|
|
80
|
+
Check(ctx *Context, node *shimast.Node)
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
// FormatRule is an optional marker contributors implement when a rule
|
|
@@ -93,20 +93,20 @@ type Rule interface {
|
|
|
93
93
|
// returning `false` is equivalent to not implementing the interface at
|
|
94
94
|
// all, and the host treats either form the same way.
|
|
95
95
|
type FormatRule interface {
|
|
96
|
-
|
|
97
|
-
|
|
96
|
+
Rule
|
|
97
|
+
IsFormat() bool
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
// Reporter is the engine-supplied callback that records a finding. The
|
|
101
101
|
// host implements this and passes it to `NewContext` when invoking a
|
|
102
102
|
// contributor rule.
|
|
103
103
|
type Reporter interface {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
104
|
+
// Report records a finding at the given node's source range.
|
|
105
|
+
Report(node *shimast.Node, message string)
|
|
106
|
+
// ReportRange records a finding at an explicit byte range inside the
|
|
107
|
+
// current file. Use this when the rule wants to highlight a
|
|
108
|
+
// sub-token.
|
|
109
|
+
ReportRange(pos, end int, message string)
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
// FixReporter is the optional extension a host implements to receive
|
|
@@ -122,8 +122,8 @@ type Reporter interface {
|
|
|
122
122
|
// a fake that wants the fix path must implement BOTH `ReportFix` and
|
|
123
123
|
// `ReportRangeFix`.
|
|
124
124
|
type FixReporter interface {
|
|
125
|
-
|
|
126
|
-
|
|
125
|
+
ReportFix(node *shimast.Node, message string, edits ...TextEdit)
|
|
126
|
+
ReportRangeFix(pos, end int, message string, edits ...TextEdit)
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
// TextEdit is one byte-range replacement offered by an autofixable finding.
|
|
@@ -143,9 +143,9 @@ type FixReporter interface {
|
|
|
143
143
|
// falls inside a deletion range. Design fixes so each finding emits one
|
|
144
144
|
// contiguous TextEdit covering the entire replacement region.
|
|
145
145
|
type TextEdit struct {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
146
|
+
Pos int
|
|
147
|
+
End int
|
|
148
|
+
Text string
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
// Context is the per-(file, rule) handle the engine passes to `Check`.
|
|
@@ -153,70 +153,70 @@ type TextEdit struct {
|
|
|
153
153
|
// contributors call `ctx.Report` / `ctx.ReportRange` directly through
|
|
154
154
|
// this Context rather than touching the reporter.
|
|
155
155
|
type Context struct {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
156
|
+
// File is the source file currently being walked. Always non-nil
|
|
157
|
+
// when `Check` is invoked.
|
|
158
|
+
File *shimast.SourceFile
|
|
159
159
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
160
|
+
// Checker is the host's tsgo type checker. Available for type-aware
|
|
161
|
+
// rules; nil-safe enough that AST-only rules can ignore it.
|
|
162
|
+
Checker *shimchecker.Checker
|
|
163
163
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
164
|
+
// Severity is the rule's resolved severity for this file. Already
|
|
165
|
+
// filtered by the engine — rules do not need to check for
|
|
166
|
+
// SeverityOff.
|
|
167
|
+
Severity Severity
|
|
168
168
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
169
|
+
// Options is the raw JSON blob the user wrote in the second slot of
|
|
170
|
+
// their `[severity, options]` rule configuration tuple. Nil when the
|
|
171
|
+
// rule was configured with a bare severity literal. Contributors that
|
|
172
|
+
// accept options decode the blob into their own struct via
|
|
173
|
+
// `(*Context).DecodeOptions`.
|
|
174
|
+
Options json.RawMessage
|
|
175
175
|
|
|
176
|
-
|
|
176
|
+
reporter Reporter
|
|
177
177
|
}
|
|
178
178
|
|
|
179
179
|
// NewContext constructs a Context for the engine to pass into a
|
|
180
180
|
// contributor rule's `Check`. Reserved for host code; contributors
|
|
181
181
|
// should not need to call this.
|
|
182
182
|
func NewContext(
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
183
|
+
file *shimast.SourceFile,
|
|
184
|
+
checker *shimchecker.Checker,
|
|
185
|
+
severity Severity,
|
|
186
|
+
options json.RawMessage,
|
|
187
|
+
reporter Reporter,
|
|
188
188
|
) *Context {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
189
|
+
return &Context{
|
|
190
|
+
File: file,
|
|
191
|
+
Checker: checker,
|
|
192
|
+
Severity: severity,
|
|
193
|
+
Options: options,
|
|
194
|
+
reporter: reporter,
|
|
195
|
+
}
|
|
196
196
|
}
|
|
197
197
|
|
|
198
198
|
// DecodeOptions unmarshals the rule's options blob into `out`. Returns
|
|
199
199
|
// nil with no side effect when the rule was configured with severity
|
|
200
200
|
// alone, so contributors can write:
|
|
201
201
|
//
|
|
202
|
-
//
|
|
203
|
-
//
|
|
204
|
-
//
|
|
202
|
+
// var opts myRuleOptions
|
|
203
|
+
// _ = ctx.DecodeOptions(&opts)
|
|
204
|
+
// // opts now holds either the user's settings or the zero value.
|
|
205
205
|
func (c *Context) DecodeOptions(out interface{}) error {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
206
|
+
if c == nil || len(c.Options) == 0 {
|
|
207
|
+
return nil
|
|
208
|
+
}
|
|
209
|
+
return json.Unmarshal(c.Options, out)
|
|
210
210
|
}
|
|
211
211
|
|
|
212
212
|
// Report records a finding at the given node's source range. Silently
|
|
213
213
|
// ignored when severity is `off` (defensive — the engine already filters
|
|
214
214
|
// by severity before invoking Check) or when no reporter is attached.
|
|
215
215
|
func (c *Context) Report(node *shimast.Node, message string) {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
216
|
+
if c == nil || c.reporter == nil || c.Severity == SeverityOff || node == nil {
|
|
217
|
+
return
|
|
218
|
+
}
|
|
219
|
+
c.reporter.Report(node, message)
|
|
220
220
|
}
|
|
221
221
|
|
|
222
222
|
// ReportFix records a finding at the given node's source range with optional
|
|
@@ -224,28 +224,28 @@ func (c *Context) Report(node *shimast.Node, message string) {
|
|
|
224
224
|
// diagnostic without edits.
|
|
225
225
|
// Treat edits as best-effort: design the rule so the diagnostic alone is useful.
|
|
226
226
|
func (c *Context) ReportFix(node *shimast.Node, message string, edits ...TextEdit) {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
227
|
+
if c == nil || c.reporter == nil || c.Severity == SeverityOff || node == nil {
|
|
228
|
+
return
|
|
229
|
+
}
|
|
230
|
+
if len(edits) == 0 {
|
|
231
|
+
c.reporter.Report(node, message)
|
|
232
|
+
return
|
|
233
|
+
}
|
|
234
|
+
fixer, ok := c.reporter.(FixReporter)
|
|
235
|
+
if !ok {
|
|
236
|
+
c.reporter.Report(node, message)
|
|
237
|
+
return
|
|
238
|
+
}
|
|
239
|
+
fixer.ReportFix(node, message, edits...)
|
|
240
240
|
}
|
|
241
241
|
|
|
242
242
|
// ReportRange records a finding at an explicit byte range inside the
|
|
243
243
|
// current file.
|
|
244
244
|
func (c *Context) ReportRange(pos, end int, message string) {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
245
|
+
if c == nil || c.reporter == nil || c.Severity == SeverityOff {
|
|
246
|
+
return
|
|
247
|
+
}
|
|
248
|
+
c.reporter.ReportRange(pos, end, message)
|
|
249
249
|
}
|
|
250
250
|
|
|
251
251
|
// ReportRangeFix records a finding at an explicit byte range with optional
|
|
@@ -253,19 +253,19 @@ func (c *Context) ReportRange(pos, end int, message string) {
|
|
|
253
253
|
// diagnostic without edits.
|
|
254
254
|
// Treat edits as best-effort: design the rule so the diagnostic alone is useful.
|
|
255
255
|
func (c *Context) ReportRangeFix(pos, end int, message string, edits ...TextEdit) {
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
256
|
+
if c == nil || c.reporter == nil || c.Severity == SeverityOff {
|
|
257
|
+
return
|
|
258
|
+
}
|
|
259
|
+
if len(edits) == 0 {
|
|
260
|
+
c.reporter.ReportRange(pos, end, message)
|
|
261
|
+
return
|
|
262
|
+
}
|
|
263
|
+
fixer, ok := c.reporter.(FixReporter)
|
|
264
|
+
if !ok {
|
|
265
|
+
c.reporter.ReportRange(pos, end, message)
|
|
266
|
+
return
|
|
267
|
+
}
|
|
268
|
+
fixer.ReportRangeFix(pos, end, message, edits...)
|
|
269
269
|
}
|
|
270
270
|
|
|
271
271
|
var registry []Rule
|
|
@@ -275,17 +275,17 @@ var registry []Rule
|
|
|
275
275
|
// — the host's adapter layer surfaces collisions with a clearer error
|
|
276
276
|
// than a raw panic.
|
|
277
277
|
func Register(r Rule) {
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
278
|
+
if r == nil {
|
|
279
|
+
panic("rule: Register called with nil rule")
|
|
280
|
+
}
|
|
281
|
+
registry = append(registry, r)
|
|
282
282
|
}
|
|
283
283
|
|
|
284
284
|
// Registered returns every contributor rule registered via `Register`.
|
|
285
285
|
// Called once by the host during engine bootstrap. The returned slice is
|
|
286
286
|
// a defensive copy so the host cannot mutate the registry.
|
|
287
287
|
func Registered() []Rule {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
288
|
+
out := make([]Rule, len(registry))
|
|
289
|
+
copy(out, registry)
|
|
290
|
+
return out
|
|
291
291
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ITtscLintFormatConfig } from "./structures/ITtscLintFormatConfig";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Documented defaults for the `format` block's *always-on* rules
|
|
@@ -7,11 +7,11 @@ import type { TtscLintFormatConfig } from "./TtscLintFormatConfig";
|
|
|
7
7
|
*
|
|
8
8
|
* Exported so users can spread defaults next to overrides:
|
|
9
9
|
*
|
|
10
|
-
* import { defaultFormat, type
|
|
10
|
+
* import { defaultFormat, type ITtscLintConfig } from "@ttsc/lint";
|
|
11
11
|
*
|
|
12
12
|
* export default {
|
|
13
13
|
* format: { ...defaultFormat, printWidth: 100 },
|
|
14
|
-
* } satisfies
|
|
14
|
+
* } satisfies ITtscLintConfig;
|
|
15
15
|
*
|
|
16
16
|
* The values mirror Prettier 1:1 except for the documented
|
|
17
17
|
* `endOfLine` narrowing (no `"cr"` / `"auto"`).
|
|
@@ -22,7 +22,7 @@ import type { TtscLintFormatConfig } from "./TtscLintFormatConfig";
|
|
|
22
22
|
* unconditionally with a non-empty `format` block.
|
|
23
23
|
*/
|
|
24
24
|
export const defaultFormat = Object.freeze({
|
|
25
|
-
severity: "
|
|
25
|
+
severity: "off",
|
|
26
26
|
semi: true,
|
|
27
27
|
singleQuote: false,
|
|
28
28
|
trailingComma: "all",
|
|
@@ -30,4 +30,4 @@ export const defaultFormat = Object.freeze({
|
|
|
30
30
|
tabWidth: 2,
|
|
31
31
|
useTabs: false,
|
|
32
32
|
endOfLine: "lf",
|
|
33
|
-
} satisfies
|
|
33
|
+
} satisfies ITtscLintFormatConfig);
|
package/src/index.ts
CHANGED
|
@@ -6,7 +6,7 @@ import path from "node:path";
|
|
|
6
6
|
|
|
7
7
|
import type { ITtscLintPlugin, ITtscLintPluginConfig } from "./structures";
|
|
8
8
|
|
|
9
|
-
export * from "./
|
|
9
|
+
export * from "./defaultFormat";
|
|
10
10
|
export * from "./structures/index";
|
|
11
11
|
|
|
12
12
|
type TtscPluginContributor = {
|
|
@@ -72,22 +72,24 @@ const LINT_CONFIG_FILENAMES = [
|
|
|
72
72
|
];
|
|
73
73
|
|
|
74
74
|
/**
|
|
75
|
-
* Plugin descriptor factory
|
|
75
|
+
* Plugin descriptor factory consumed by ttsc package discovery.
|
|
76
76
|
*
|
|
77
77
|
* Two discovery surfaces feed the descriptor's `contributors` field:
|
|
78
78
|
*
|
|
79
79
|
* 1. The tsconfig plugin entry's `plugins` map — namespace → npm specifier. Inline
|
|
80
80
|
* for projects that prefer to keep everything in `tsconfig.json`.
|
|
81
81
|
* 2. The companion `lint.config.{ts,cts,mts,js,cjs,mjs,json}` (or
|
|
82
|
-
* `eslint.config.*`) file —
|
|
82
|
+
* `eslint.config.*`) file — an object with an in-memory `plugins: {
|
|
83
83
|
* ns: pluginObject }` map. The factory evaluates the config (via ttsx for TS
|
|
84
|
-
* / ESM sources, `require` for CommonJS, `JSON.parse` for JSON) and
|
|
85
|
-
*
|
|
84
|
+
* / ESM sources, `require` for CommonJS, `JSON.parse` for JSON) and reads
|
|
85
|
+
* the `plugins` field.
|
|
86
86
|
*
|
|
87
87
|
* Contributions from both sources are merged with the tsconfig entry winning on
|
|
88
88
|
* namespace collisions, so a project can opt into a hand-curated subset of an
|
|
89
89
|
* external `lint.config.ts` by overriding specific namespaces in
|
|
90
90
|
* `tsconfig.json`.
|
|
91
|
+
*
|
|
92
|
+
* @internal
|
|
91
93
|
*/
|
|
92
94
|
export default function createTtscPlugin(
|
|
93
95
|
context: TtscPluginFactoryContext<ITtscLintPluginConfig>,
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ITtscLintFormatConfig } from "./ITtscLintFormatConfig";
|
|
2
|
+
import type { ITtscLintPlugin } from "./ITtscLintPlugin";
|
|
3
|
+
import type { TtscLintRuleMap } from "./TtscLintRuleMap";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Top-level object accepted by `@ttsc/lint` config files.
|
|
7
|
+
*
|
|
8
|
+
* Keep the file shape plain: users export an object and use
|
|
9
|
+
* `satisfies ITtscLintConfig` when they want type checking.
|
|
10
|
+
*/
|
|
11
|
+
export interface ITtscLintConfig {
|
|
12
|
+
/** Globs that select the files this entry applies to. */
|
|
13
|
+
files?: string | readonly string[];
|
|
14
|
+
|
|
15
|
+
/** Globs that exclude files this entry would otherwise match. */
|
|
16
|
+
ignores?: string | readonly string[];
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Config file path folded in before this object's own rules apply.
|
|
20
|
+
*
|
|
21
|
+
* Relative paths resolve from the containing config file's directory.
|
|
22
|
+
*/
|
|
23
|
+
extends?: string;
|
|
24
|
+
|
|
25
|
+
/** Contributor plugin objects keyed by namespace. */
|
|
26
|
+
plugins?: Record<string, ITtscLintPlugin>;
|
|
27
|
+
|
|
28
|
+
/** Rule-name to severity map. Supports severity tuples with options. */
|
|
29
|
+
rules?: TtscLintRuleMap;
|
|
30
|
+
|
|
31
|
+
/** Prettier-style flat configuration for the `format/*` rules. */
|
|
32
|
+
format?: ITtscLintFormatConfig;
|
|
33
|
+
}
|