@ttsc/lint 0.8.0-dev.20260505 → 0.8.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.
@@ -0,0 +1,296 @@
1
+ package main
2
+
3
+ import (
4
+ "strings"
5
+
6
+ shimast "github.com/microsoft/typescript-go/shim/ast"
7
+ shimscanner "github.com/microsoft/typescript-go/shim/scanner"
8
+ )
9
+
10
+ type lintDirectiveKind int
11
+
12
+ const (
13
+ lintDirectiveDisable lintDirectiveKind = iota
14
+ lintDirectiveEnable
15
+ lintDirectiveDisableLine
16
+ lintDirectiveDisableNextLine
17
+ )
18
+
19
+ type lintDirective struct {
20
+ kind lintDirectiveKind
21
+ rules lintDirectiveRules
22
+ }
23
+
24
+ type lintDirectiveRules struct {
25
+ all bool
26
+ rules map[string]struct{}
27
+ }
28
+
29
+ type lintDirectiveEvent struct {
30
+ pos int
31
+ rules lintDirectiveRules
32
+ on bool
33
+ }
34
+
35
+ type lintInlineDirectives struct {
36
+ lines map[int][]lintDirectiveRules
37
+ events []lintDirectiveEvent
38
+ }
39
+
40
+ type lintDisableState struct {
41
+ all bool
42
+ rules map[string]struct{}
43
+ enabledInAll map[string]struct{}
44
+ }
45
+
46
+ func filterInlineDisabledFindings(file *shimast.SourceFile, findings []*Finding) []*Finding {
47
+ if len(findings) == 0 || file == nil {
48
+ return findings
49
+ }
50
+ directives := parseLintInlineDirectives(file)
51
+ if directives.empty() {
52
+ return findings
53
+ }
54
+ filtered := findings[:0]
55
+ for _, finding := range findings {
56
+ if finding == nil || !directives.suppresses(file, finding) {
57
+ filtered = append(filtered, finding)
58
+ }
59
+ }
60
+ return filtered
61
+ }
62
+
63
+ func parseLintInlineDirectives(file *shimast.SourceFile) *lintInlineDirectives {
64
+ directives := &lintInlineDirectives{
65
+ lines: make(map[int][]lintDirectiveRules),
66
+ }
67
+ scanner := shimscanner.NewScanner()
68
+ scanner.SetText(file.Text())
69
+ scanner.SetSkipTrivia(false)
70
+
71
+ scan:
72
+ for {
73
+ kind := scanner.Scan()
74
+ switch kind {
75
+ case shimast.KindEndOfFile:
76
+ break scan
77
+ case shimast.KindSingleLineCommentTrivia, shimast.KindMultiLineCommentTrivia:
78
+ default:
79
+ continue
80
+ }
81
+ directive, ok := parseLintDirectiveComment(scanner.TokenText())
82
+ if !ok {
83
+ continue
84
+ }
85
+ start := scanner.TokenStart()
86
+ end := scanner.TokenEnd()
87
+ startLine := shimscanner.GetECMALineOfPosition(file, start)
88
+ endLine := startLine
89
+ if end > start {
90
+ endLine = shimscanner.GetECMALineOfPosition(file, end-1)
91
+ }
92
+ switch directive.kind {
93
+ case lintDirectiveDisableLine:
94
+ directives.lines[startLine] = append(directives.lines[startLine], directive.rules)
95
+ case lintDirectiveDisableNextLine:
96
+ directives.lines[endLine+1] = append(directives.lines[endLine+1], directive.rules)
97
+ case lintDirectiveDisable:
98
+ directives.events = append(directives.events, lintDirectiveEvent{
99
+ pos: start,
100
+ rules: directive.rules,
101
+ on: true,
102
+ })
103
+ case lintDirectiveEnable:
104
+ directives.events = append(directives.events, lintDirectiveEvent{
105
+ pos: start,
106
+ rules: directive.rules,
107
+ on: false,
108
+ })
109
+ }
110
+ }
111
+ return directives
112
+ }
113
+
114
+ func (d *lintInlineDirectives) empty() bool {
115
+ return d == nil || (len(d.lines) == 0 && len(d.events) == 0)
116
+ }
117
+
118
+ func (d *lintInlineDirectives) suppresses(file *shimast.SourceFile, finding *Finding) bool {
119
+ if d == nil || finding == nil || file == nil {
120
+ return false
121
+ }
122
+ line := shimscanner.GetECMALineOfPosition(file, finding.Pos)
123
+ for _, rules := range d.lines[line] {
124
+ if rules.matches(finding.Rule) {
125
+ return true
126
+ }
127
+ }
128
+ var state lintDisableState
129
+ for _, event := range d.events {
130
+ if event.pos > finding.Pos {
131
+ break
132
+ }
133
+ state.apply(event)
134
+ }
135
+ return state.matches(finding.Rule)
136
+ }
137
+
138
+ func (s *lintDisableState) apply(event lintDirectiveEvent) {
139
+ if event.on {
140
+ if event.rules.all {
141
+ s.all = true
142
+ s.enabledInAll = nil
143
+ return
144
+ }
145
+ if s.rules == nil {
146
+ s.rules = make(map[string]struct{}, len(event.rules.rules))
147
+ }
148
+ for rule := range event.rules.rules {
149
+ s.rules[rule] = struct{}{}
150
+ delete(s.enabledInAll, rule)
151
+ }
152
+ return
153
+ }
154
+
155
+ if event.rules.all {
156
+ s.all = false
157
+ s.rules = nil
158
+ s.enabledInAll = nil
159
+ return
160
+ }
161
+ for rule := range event.rules.rules {
162
+ delete(s.rules, rule)
163
+ if s.all {
164
+ if s.enabledInAll == nil {
165
+ s.enabledInAll = make(map[string]struct{}, len(event.rules.rules))
166
+ }
167
+ s.enabledInAll[rule] = struct{}{}
168
+ }
169
+ }
170
+ }
171
+
172
+ func (s lintDisableState) matches(rule string) bool {
173
+ normalized := normalizeDirectiveRuleName(rule)
174
+ if _, ok := s.rules[normalized]; ok {
175
+ return true
176
+ }
177
+ if !s.all {
178
+ return false
179
+ }
180
+ _, enabled := s.enabledInAll[normalized]
181
+ return !enabled
182
+ }
183
+
184
+ func (r lintDirectiveRules) matches(rule string) bool {
185
+ if r.all {
186
+ return true
187
+ }
188
+ _, ok := r.rules[normalizeDirectiveRuleName(rule)]
189
+ return ok
190
+ }
191
+
192
+ func parseLintDirectiveComment(raw string) (lintDirective, bool) {
193
+ text := stripCommentDelimiters(raw)
194
+ if directive, ok := parseLintDirectiveLine(text); ok {
195
+ return directive, true
196
+ }
197
+ return lintDirective{}, false
198
+ }
199
+
200
+ func stripCommentDelimiters(raw string) string {
201
+ switch {
202
+ case strings.HasPrefix(raw, "//"):
203
+ return strings.TrimSpace(raw[2:])
204
+ case strings.HasPrefix(raw, "/*"):
205
+ text := raw[2:]
206
+ if strings.HasSuffix(text, "*/") {
207
+ text = text[:len(text)-2]
208
+ }
209
+ text = strings.TrimSpace(text)
210
+ if strings.HasPrefix(text, "*") {
211
+ text = strings.TrimSpace(text[1:])
212
+ }
213
+ return text
214
+ default:
215
+ return strings.TrimSpace(raw)
216
+ }
217
+ }
218
+
219
+ func parseLintDirectiveLine(text string) (lintDirective, bool) {
220
+ for _, prefix := range []string{"eslint", "lint"} {
221
+ for _, form := range []struct {
222
+ suffix string
223
+ kind lintDirectiveKind
224
+ }{
225
+ {"disable-next-line", lintDirectiveDisableNextLine},
226
+ {"disable-line", lintDirectiveDisableLine},
227
+ {"disable", lintDirectiveDisable},
228
+ {"enable", lintDirectiveEnable},
229
+ } {
230
+ marker := prefix + "-" + form.suffix
231
+ payload, ok := directivePayload(text, marker)
232
+ if !ok {
233
+ continue
234
+ }
235
+ return lintDirective{
236
+ kind: form.kind,
237
+ rules: parseDirectiveRules(payload),
238
+ }, true
239
+ }
240
+ }
241
+ return lintDirective{}, false
242
+ }
243
+
244
+ func directivePayload(text, marker string) (string, bool) {
245
+ if !strings.HasPrefix(text, marker) {
246
+ return "", false
247
+ }
248
+ rest := text[len(marker):]
249
+ if rest != "" && rest[0] != ' ' && rest[0] != '\t' && rest[0] != '\r' && rest[0] != '\n' {
250
+ return "", false
251
+ }
252
+ return strings.TrimSpace(rest), true
253
+ }
254
+
255
+ func parseDirectiveRules(payload string) lintDirectiveRules {
256
+ payload = stripDirectiveDescription(payload)
257
+ payload = strings.ReplaceAll(payload, ",", " ")
258
+ fields := strings.Fields(payload)
259
+ rules := make(map[string]struct{}, len(fields))
260
+ for _, field := range fields {
261
+ if strings.HasPrefix(field, "--") {
262
+ break
263
+ }
264
+ name := normalizeDirectiveRuleName(field)
265
+ if name != "" {
266
+ rules[name] = struct{}{}
267
+ }
268
+ }
269
+ if len(rules) == 0 {
270
+ return lintDirectiveRules{all: true}
271
+ }
272
+ return lintDirectiveRules{rules: rules}
273
+ }
274
+
275
+ func stripDirectiveDescription(payload string) string {
276
+ for i := 0; i < len(payload)-1; i++ {
277
+ if payload[i] != '-' || payload[i+1] != '-' {
278
+ continue
279
+ }
280
+ prevOK := i == 0 || payload[i-1] == ' ' || payload[i-1] == '\t'
281
+ next := i + 2
282
+ nextOK := next >= len(payload) || payload[next] == ' ' || payload[next] == '\t'
283
+ if prevOK && nextOK {
284
+ return strings.TrimSpace(payload[:i])
285
+ }
286
+ }
287
+ return payload
288
+ }
289
+
290
+ func normalizeDirectiveRuleName(name string) string {
291
+ name = strings.TrimSpace(name)
292
+ name = strings.TrimPrefix(name, "@typescript-eslint/")
293
+ name = strings.TrimPrefix(name, "typescript-eslint/")
294
+ name = strings.TrimPrefix(name, "eslint/")
295
+ return name
296
+ }
package/plugin/engine.go CHANGED
@@ -145,7 +145,7 @@ func AllRuleNames() []string {
145
145
  // Engine binds a rule configuration to a Program and walks the AST once
146
146
  // per source file, dispatching each visited node to its interested rules.
147
147
  type Engine struct {
148
- config RuleConfig
148
+ config RuleResolver
149
149
  rules map[shimast.Kind][]Rule
150
150
  enabled map[string]Severity
151
151
  unknown []string
@@ -156,21 +156,28 @@ type Engine struct {
156
156
  // an unknown rule are recorded so the caller can surface them as a
157
157
  // configuration warning rather than a silent typo.
158
158
  func NewEngine(config RuleConfig) *Engine {
159
+ return NewEngineWithResolver(config)
160
+ }
161
+
162
+ // NewEngineWithResolver returns an engine configured by a resolver that can
163
+ // vary rule severities per file.
164
+ func NewEngineWithResolver(config RuleResolver) *Engine {
165
+ if config == nil {
166
+ config = RuleConfig{}
167
+ }
159
168
  eng := &Engine{
160
169
  config: config,
161
170
  rules: make(map[shimast.Kind][]Rule),
162
171
  enabled: make(map[string]Severity),
163
172
  }
164
- for name, sev := range config {
173
+ displaySeverities := config.EnabledRuleConfig()
174
+ for _, name := range config.ActiveRuleNames() {
165
175
  rule, ok := registered.rules[name]
166
176
  if !ok {
167
177
  eng.unknown = append(eng.unknown, name)
168
178
  continue
169
179
  }
170
- if sev == SeverityOff {
171
- continue
172
- }
173
- eng.enabled[name] = sev
180
+ eng.enabled[name] = displaySeverities.Severity(name)
174
181
  for _, kind := range rule.Visits() {
175
182
  eng.rules[kind] = append(eng.rules[kind], rule)
176
183
  }
@@ -206,6 +213,11 @@ func (e *Engine) Run(files []*shimast.SourceFile, checker *shimchecker.Checker)
206
213
  func (e *Engine) runFile(file *shimast.SourceFile, checker *shimchecker.Checker) []*Finding {
207
214
  var collected []*Finding
208
215
  collect := func(f *Finding) { collected = append(collected, f) }
216
+ resolved := e.config.ResolveRules(file.FileName())
217
+ if resolved.Ignored {
218
+ return collected
219
+ }
220
+ fileRules := resolved.Rules
209
221
 
210
222
  var walk func(node *shimast.Node)
211
223
  walk = func(node *shimast.Node) {
@@ -214,10 +226,14 @@ func (e *Engine) runFile(file *shimast.SourceFile, checker *shimchecker.Checker)
214
226
  }
215
227
  if rules, ok := e.rules[node.Kind]; ok {
216
228
  for _, rule := range rules {
229
+ severity := fileRules.Severity(rule.Name())
230
+ if severity == SeverityOff {
231
+ continue
232
+ }
217
233
  ctx := &Context{
218
234
  File: file,
219
235
  Checker: checker,
220
- Severity: e.enabled[rule.Name()],
236
+ Severity: severity,
221
237
  rule: rule,
222
238
  collect: collect,
223
239
  }
@@ -236,10 +252,14 @@ func (e *Engine) runFile(file *shimast.SourceFile, checker *shimchecker.Checker)
236
252
  // SourceFile).
237
253
  if rules, ok := e.rules[shimast.KindSourceFile]; ok {
238
254
  for _, rule := range rules {
255
+ severity := fileRules.Severity(rule.Name())
256
+ if severity == SeverityOff {
257
+ continue
258
+ }
239
259
  ctx := &Context{
240
260
  File: file,
241
261
  Checker: checker,
242
- Severity: e.enabled[rule.Name()],
262
+ Severity: severity,
243
263
  rule: rule,
244
264
  collect: collect,
245
265
  }
@@ -254,5 +274,5 @@ func (e *Engine) runFile(file *shimast.SourceFile, checker *shimchecker.Checker)
254
274
  for _, stmt := range statements.Nodes {
255
275
  walk(stmt)
256
276
  }
257
- return collected
277
+ return filterInlineDisabledFindings(file, collected)
258
278
  }
@@ -0,0 +1,246 @@
1
+ package main
2
+
3
+ import (
4
+ "encoding/json"
5
+ "fmt"
6
+ "os"
7
+ "os/exec"
8
+ "path/filepath"
9
+ "strings"
10
+ "unicode/utf8"
11
+
12
+ shimast "github.com/microsoft/typescript-go/shim/ast"
13
+ shimdw "github.com/microsoft/typescript-go/shim/diagnosticwriter"
14
+ )
15
+
16
+ type eslintRuntimeProvider interface {
17
+ ExternalConfigPath() string
18
+ WantsESLintRuntime() bool
19
+ RequiresESLintRuntime() bool
20
+ }
21
+
22
+ type eslintRuntimeOutput struct {
23
+ Missing bool `json:"missing"`
24
+ Results []eslintRuntimeFile `json:"results"`
25
+ }
26
+
27
+ type eslintRuntimeFile struct {
28
+ FilePath string `json:"filePath"`
29
+ Messages []eslintRuntimeMessage `json:"messages"`
30
+ }
31
+
32
+ type eslintRuntimeMessage struct {
33
+ RuleID string `json:"ruleId"`
34
+ Severity int `json:"severity"`
35
+ Message string `json:"message"`
36
+ Line int `json:"line"`
37
+ Column int `json:"column"`
38
+ EndLine int `json:"endLine"`
39
+ EndColumn int `json:"endColumn"`
40
+ }
41
+
42
+ func runExternalESLintDiagnostics(
43
+ resolver RuleResolver,
44
+ cwd string,
45
+ files []*shimast.SourceFile,
46
+ ) ([]*shimdw.LintDiagnostic, bool, error) {
47
+ provider, ok := resolver.(eslintRuntimeProvider)
48
+ if !ok || !provider.WantsESLintRuntime() {
49
+ return nil, false, nil
50
+ }
51
+ configPath := provider.ExternalConfigPath()
52
+ if configPath == "" {
53
+ return nil, false, nil
54
+ }
55
+
56
+ fileNames := make([]string, 0, len(files))
57
+ byPath := make(map[string]*shimast.SourceFile, len(files))
58
+ for _, file := range files {
59
+ if file == nil || file.IsDeclarationFile {
60
+ continue
61
+ }
62
+ name := file.FileName()
63
+ if !filepath.IsAbs(name) {
64
+ name = filepath.Join(cwd, name)
65
+ }
66
+ if abs, err := filepath.Abs(name); err == nil {
67
+ name = abs
68
+ }
69
+ fileNames = append(fileNames, name)
70
+ byPath[filepath.ToSlash(name)] = file
71
+ }
72
+ if len(fileNames) == 0 {
73
+ return nil, true, nil
74
+ }
75
+
76
+ payload, err := json.Marshal(fileNames)
77
+ if err != nil {
78
+ return nil, false, fmt.Errorf("@ttsc/lint: encode ESLint file list: %w", err)
79
+ }
80
+
81
+ output, err := runExternalESLint(cwd, configPath, string(payload))
82
+ if err != nil {
83
+ return nil, false, err
84
+ }
85
+ if output.Missing {
86
+ if provider.RequiresESLintRuntime() {
87
+ return nil, false, fmt.Errorf("@ttsc/lint: ESLint runtime is required by %s; install eslint in the project or replace runtime-only config features", configPath)
88
+ }
89
+ return nil, false, nil
90
+ }
91
+
92
+ diagnostics := make([]*shimdw.LintDiagnostic, 0)
93
+ for _, result := range output.Results {
94
+ file := byPath[filepath.ToSlash(result.FilePath)]
95
+ if file == nil {
96
+ continue
97
+ }
98
+ for _, msg := range result.Messages {
99
+ if msg.Severity == 0 {
100
+ continue
101
+ }
102
+ ruleID := strings.TrimSpace(msg.RuleID)
103
+ if ruleID == "" {
104
+ ruleID = "eslint"
105
+ }
106
+ category := shimdw.LintCategoryWarning
107
+ if msg.Severity >= 2 {
108
+ category = shimdw.LintCategoryError
109
+ }
110
+ pos := positionOfESLintLocation(file.Text(), msg.Line, msg.Column)
111
+ end := positionOfESLintLocation(file.Text(), msg.EndLine, msg.EndColumn)
112
+ if end <= pos {
113
+ end = pos + 1
114
+ }
115
+ diagnostics = append(diagnostics, shimdw.NewLintDiagnostic(
116
+ file,
117
+ pos,
118
+ end,
119
+ ruleCode(ruleID),
120
+ category,
121
+ fmt.Sprintf("[%s] %s", ruleID, msg.Message),
122
+ ))
123
+ }
124
+ }
125
+ return diagnostics, true, nil
126
+ }
127
+
128
+ func runExternalESLint(cwd, configPath, fileListJSON string) (*eslintRuntimeOutput, error) {
129
+ node := os.Getenv("TTSC_NODE_BINARY")
130
+ if node == "" {
131
+ node = "node"
132
+ }
133
+ cmd := exec.Command(node, "-e", externalESLintRunnerScript, cwd, configPath, fileListJSON)
134
+ cmd.Env = nodeConfigLoaderEnv(configPath)
135
+ cmd.Dir = cwd
136
+ raw, err := cmd.Output()
137
+ if err != nil {
138
+ stderr := ""
139
+ if exit, ok := err.(*exec.ExitError); ok {
140
+ stderr = strings.TrimSpace(string(exit.Stderr))
141
+ }
142
+ if stderr != "" {
143
+ return nil, fmt.Errorf("@ttsc/lint: run ESLint config %s: %s", configPath, stderr)
144
+ }
145
+ return nil, fmt.Errorf("@ttsc/lint: run ESLint config %s: %w", configPath, err)
146
+ }
147
+ var output eslintRuntimeOutput
148
+ if err := json.Unmarshal(raw, &output); err != nil {
149
+ return nil, fmt.Errorf("@ttsc/lint: parse ESLint output for %s: %w", configPath, err)
150
+ }
151
+ return &output, nil
152
+ }
153
+
154
+ func positionOfESLintLocation(text string, line, column int) int {
155
+ if line <= 0 {
156
+ line = 1
157
+ }
158
+ if column <= 0 {
159
+ column = 1
160
+ }
161
+ lineStart := 0
162
+ currentLine := 1
163
+ for i := 0; i < len(text) && currentLine < line; {
164
+ r, size := utf8.DecodeRuneInString(text[i:])
165
+ if r == '\n' {
166
+ currentLine++
167
+ lineStart = i + size
168
+ }
169
+ i += size
170
+ }
171
+ targetUTF16 := column - 1
172
+ seenUTF16 := 0
173
+ for i := lineStart; i < len(text); {
174
+ if seenUTF16 >= targetUTF16 {
175
+ return i
176
+ }
177
+ r, size := utf8.DecodeRuneInString(text[i:])
178
+ if r == '\n' || r == '\r' {
179
+ return i
180
+ }
181
+ if r > 0xFFFF {
182
+ seenUTF16 += 2
183
+ } else {
184
+ seenUTF16++
185
+ }
186
+ i += size
187
+ }
188
+ return len(text)
189
+ }
190
+
191
+ const externalESLintRunnerScript = `
192
+ const { createRequire } = require("node:module");
193
+ const path = require("node:path");
194
+
195
+ (async () => {
196
+ const cwd = process.argv[1];
197
+ const configPath = process.argv[2];
198
+ const files = JSON.parse(process.argv[3]);
199
+ const requireFromProject = createRequire(path.join(cwd, "package.json"));
200
+ let eslintPath;
201
+ try {
202
+ eslintPath = requireFromProject.resolve("eslint");
203
+ } catch (error) {
204
+ if (error && error.code === "MODULE_NOT_FOUND") {
205
+ process.stdout.write(JSON.stringify({ missing: true, results: [] }));
206
+ return;
207
+ }
208
+ throw error;
209
+ }
210
+ const eslintModule = requireFromProject(eslintPath);
211
+
212
+ const ESLintCtor = typeof eslintModule.loadESLint === "function"
213
+ ? await eslintModule.loadESLint({ useFlatConfig: true })
214
+ : eslintModule.ESLint ?? eslintModule.default?.ESLint ?? eslintModule.default;
215
+
216
+ if (typeof ESLintCtor !== "function") {
217
+ throw new Error("installed eslint package does not export ESLint or loadESLint");
218
+ }
219
+
220
+ const eslint = new ESLintCtor({
221
+ cwd,
222
+ overrideConfigFile: configPath,
223
+ ignore: true,
224
+ warnIgnored: false,
225
+ });
226
+ const results = await eslint.lintFiles(files);
227
+ process.stdout.write(JSON.stringify({
228
+ missing: false,
229
+ results: results.map((result) => ({
230
+ filePath: result.filePath,
231
+ messages: result.messages.map((message) => ({
232
+ ruleId: message.ruleId || "eslint",
233
+ severity: message.severity,
234
+ message: message.message,
235
+ line: message.line || 1,
236
+ column: message.column || 1,
237
+ endLine: message.endLine || message.line || 1,
238
+ endColumn: message.endColumn || message.column || 1,
239
+ })),
240
+ })),
241
+ }));
242
+ })().catch((error) => {
243
+ process.stderr.write(error && error.stack ? error.stack : String(error));
244
+ process.exit(1);
245
+ });
246
+ `
package/src/config.ts ADDED
@@ -0,0 +1,3 @@
1
+ export type { TtscLintConfig } from "./structures/TtscLintConfig";
2
+ export type { TtscLintRule } from "./structures/TtscLintRule";
3
+ export type { TtscLintSeverity } from "./structures/TtscLintSeverity";
@@ -5,10 +5,11 @@ import type { TtscLintConfig } from "./TtscLintConfig";
5
5
  /** `compilerOptions.plugins[]` entry shape consumed by `@ttsc/lint`. */
6
6
  export interface ITtscLintPluginConfig extends ITtscProjectPluginConfig {
7
7
  /**
8
- * Inline rule map or path to a JSON config file.
8
+ * Inline rule map or path to a standalone lint config file.
9
9
  *
10
10
  * Inline maps are passed to the native sidecar through `--plugins-json`.
11
- * String values are resolved by the sidecar from the project root.
11
+ * String values are resolved by the sidecar from the owning tsconfig
12
+ * directory and may point at JSON, JavaScript, or TypeScript config files.
12
13
  */
13
14
  config?: string | TtscLintConfig;
14
15
  }
@@ -4,9 +4,8 @@ import type { TtscLintSeverity } from "./TtscLintSeverity";
4
4
  /**
5
5
  * Inline rule map accepted by the `@ttsc/lint` tsconfig plugin entry.
6
6
  *
7
- * Each property key is a native lint rule name. Omitted rules use the sidecar's
8
- * default severity, while present rules override that severity for the current
9
- * project.
7
+ * Each property key is a native lint rule name. Omitted rules are disabled,
8
+ * while present rules enable or disable that rule for the current project.
10
9
  */
11
10
  export type TtscLintConfig = {
12
11
  [P in TtscLintRule]?: TtscLintSeverity;