@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.
Files changed (57) hide show
  1. package/README.md +67 -141
  2. package/lib/{structures/defaultFormat.d.ts → defaultFormat.d.ts} +3 -3
  3. package/lib/{structures/defaultFormat.js → defaultFormat.js} +3 -3
  4. package/lib/defaultFormat.js.map +1 -0
  5. package/lib/index.d.ts +1 -38
  6. package/lib/index.js +7 -5
  7. package/lib/index.js.map +1 -1
  8. package/lib/structures/ITtscLintConfig.d.ts +27 -0
  9. package/lib/structures/{PluginRuleNames.js → ITtscLintConfig.js} +1 -1
  10. package/lib/structures/ITtscLintConfig.js.map +1 -0
  11. package/lib/structures/{TtscLintFormatConfig.d.ts → ITtscLintFormatConfig.d.ts} +17 -16
  12. package/lib/structures/{TtscLintConfig.js → ITtscLintFormatConfig.js} +1 -1
  13. package/lib/structures/ITtscLintFormatConfig.js.map +1 -0
  14. package/lib/structures/ITtscLintPluginConfig.d.ts +6 -6
  15. package/lib/structures/TtscLintRuleMap.d.ts +11 -9
  16. package/lib/structures/TtscLintRuleOptions.d.ts +118 -144
  17. package/lib/structures/index.d.ts +2 -7
  18. package/lib/structures/index.js +2 -7
  19. package/lib/structures/index.js.map +1 -1
  20. package/linthost/config.go +1 -6
  21. package/linthost/config_format.go +253 -298
  22. package/linthost/format.go +91 -1
  23. package/package.json +3 -3
  24. package/rule/rule.go +124 -124
  25. package/src/{structures/defaultFormat.ts → defaultFormat.ts} +5 -5
  26. package/src/index.ts +7 -5
  27. package/src/structures/ITtscLintConfig.ts +33 -0
  28. package/src/structures/{TtscLintFormatConfig.ts → ITtscLintFormatConfig.ts} +17 -16
  29. package/src/structures/ITtscLintPluginConfig.ts +6 -6
  30. package/src/structures/TtscLintRuleMap.ts +17 -13
  31. package/src/structures/TtscLintRuleOptions.ts +118 -144
  32. package/src/structures/index.ts +2 -7
  33. package/lib/defineConfig.d.ts +0 -55
  34. package/lib/defineConfig.js +0 -39
  35. package/lib/defineConfig.js.map +0 -1
  36. package/lib/structures/PluginRuleNames.d.ts +0 -14
  37. package/lib/structures/PluginRuleNames.js.map +0 -1
  38. package/lib/structures/TtscLintConfig.d.ts +0 -27
  39. package/lib/structures/TtscLintConfig.js.map +0 -1
  40. package/lib/structures/TtscLintConfigEntry.d.ts +0 -39
  41. package/lib/structures/TtscLintConfigEntry.js +0 -3
  42. package/lib/structures/TtscLintConfigEntry.js.map +0 -1
  43. package/lib/structures/TtscLintFormatConfig.js +0 -3
  44. package/lib/structures/TtscLintFormatConfig.js.map +0 -1
  45. package/lib/structures/TtscLintPlugins.d.ts +0 -9
  46. package/lib/structures/TtscLintPlugins.js +0 -3
  47. package/lib/structures/TtscLintPlugins.js.map +0 -1
  48. package/lib/structures/TtscLintRuleEntry.d.ts +0 -22
  49. package/lib/structures/TtscLintRuleEntry.js +0 -3
  50. package/lib/structures/TtscLintRuleEntry.js.map +0 -1
  51. package/lib/structures/defaultFormat.js.map +0 -1
  52. package/src/defineConfig.ts +0 -69
  53. package/src/structures/PluginRuleNames.ts +0 -19
  54. package/src/structures/TtscLintConfig.ts +0 -31
  55. package/src/structures/TtscLintConfigEntry.ts +0 -50
  56. package/src/structures/TtscLintPlugins.ts +0 -10
  57. package/src/structures/TtscLintRuleEntry.ts +0 -28
@@ -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.11.0-dev.20260518-2",
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.11.0-dev.20260518-2"
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://github.com/samchon/ttsc/tree/master/packages/lint#readme",
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's first-party
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
- // package myrules
24
+ // package myrules
25
25
  //
26
- // import (
27
- // shimast "github.com/microsoft/typescript-go/shim/ast"
28
- // "github.com/samchon/ttsc/packages/lint/rule"
29
- // )
26
+ // import (
27
+ // shimast "github.com/microsoft/typescript-go/shim/ast"
28
+ // "github.com/samchon/ttsc/packages/lint/rule"
29
+ // )
30
30
  //
31
- // func init() { rule.Register(noTodoComment{}) }
31
+ // func init() { rule.Register(noTodoComment{}) }
32
32
  //
33
- // type noTodoComment struct{}
33
+ // type noTodoComment struct{}
34
34
  //
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
- // }
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
- "encoding/json"
44
+ "encoding/json"
45
45
 
46
- shimast "github.com/microsoft/typescript-go/shim/ast"
47
- shimchecker "github.com/microsoft/typescript-go/shim/checker"
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
- // 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
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
- // 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
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
- // 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
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
- // Check is invoked once per relevant node. Use `ctx.Report` /
79
- // `ctx.ReportRange` to emit findings.
80
- Check(ctx *Context, node *shimast.Node)
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
- Rule
97
- IsFormat() bool
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
- // 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)
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
- ReportFix(node *shimast.Node, message string, edits ...TextEdit)
126
- ReportRangeFix(pos, end int, message string, edits ...TextEdit)
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
- Pos int
147
- End int
148
- Text string
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
- // File is the source file currently being walked. Always non-nil
157
- // when `Check` is invoked.
158
- File *shimast.SourceFile
156
+ // File is the source file currently being walked. Always non-nil
157
+ // when `Check` is invoked.
158
+ File *shimast.SourceFile
159
159
 
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
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
- // 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
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
- // 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
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
- reporter Reporter
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
- file *shimast.SourceFile,
184
- checker *shimchecker.Checker,
185
- severity Severity,
186
- options json.RawMessage,
187
- reporter Reporter,
183
+ file *shimast.SourceFile,
184
+ checker *shimchecker.Checker,
185
+ severity Severity,
186
+ options json.RawMessage,
187
+ reporter Reporter,
188
188
  ) *Context {
189
- return &Context{
190
- File: file,
191
- Checker: checker,
192
- Severity: severity,
193
- Options: options,
194
- reporter: reporter,
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
- // var opts myRuleOptions
203
- // _ = ctx.DecodeOptions(&opts)
204
- // // opts now holds either the user's settings or the zero value.
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
- if c == nil || len(c.Options) == 0 {
207
- return nil
208
- }
209
- return json.Unmarshal(c.Options, out)
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
- if c == nil || c.reporter == nil || c.Severity == SeverityOff || node == nil {
217
- return
218
- }
219
- c.reporter.Report(node, message)
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
- 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...)
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
- if c == nil || c.reporter == nil || c.Severity == SeverityOff {
246
- return
247
- }
248
- c.reporter.ReportRange(pos, end, message)
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
- 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...)
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
- if r == nil {
279
- panic("rule: Register called with nil rule")
280
- }
281
- registry = append(registry, r)
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
- out := make([]Rule, len(registry))
289
- copy(out, registry)
290
- return out
288
+ out := make([]Rule, len(registry))
289
+ copy(out, registry)
290
+ return out
291
291
  }
@@ -1,4 +1,4 @@
1
- import type { TtscLintFormatConfig } from "./TtscLintFormatConfig";
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 TtscLintConfig } from "@ttsc/lint";
10
+ * import { defaultFormat, type ITtscLintConfig } from "@ttsc/lint";
11
11
  *
12
12
  * export default {
13
13
  * format: { ...defaultFormat, printWidth: 100 },
14
- * } satisfies TtscLintConfig;
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: "warning",
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 TtscLintFormatConfig);
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 "./defineConfig";
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 for `@ttsc/lint`.
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 — flat-config-style with an in-memory `plugins: {
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 walks
85
- * every entry's `plugins` field.
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
+ }