1use std::{fmt, fmt::Write as _, str::FromStr, sync::Arc};
2
3use crate::source::ScriptKind;
4
5#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
7pub enum LintLevel {
8 Allow,
9 Warn,
10 Deny,
11 Forbid,
12}
13
14impl LintLevel {
15 #[must_use]
16 pub const fn as_str(self) -> &'static str {
17 match self {
18 Self::Allow => "allow",
19 Self::Warn => "warn",
20 Self::Deny => "deny",
21 Self::Forbid => "forbid",
22 }
23 }
24}
25
26impl fmt::Display for LintLevel {
27 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
28 formatter.write_str(self.as_str())
29 }
30}
31
32impl FromStr for LintLevel {
33 type Err = ParseLintLevelError;
34
35 fn from_str(value: &str) -> Result<Self, Self::Err> {
36 match value {
37 "allow" => Ok(Self::Allow),
38 "warn" => Ok(Self::Warn),
39 "deny" => Ok(Self::Deny),
40 "forbid" => Ok(Self::Forbid),
41 _ => Err(ParseLintLevelError(Arc::from(value))),
42 }
43 }
44}
45
46#[derive(Clone, Debug, Eq, PartialEq)]
47pub struct ParseLintLevelError(Arc<str>);
48
49impl fmt::Display for ParseLintLevelError {
50 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
51 write!(formatter, "unknown lint level {:?}", self.0)
52 }
53}
54
55impl std::error::Error for ParseLintLevelError {}
56
57#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
59pub enum RuleGroup {
60 Unsoundness,
61 EscapeHatches,
62 NonErasable,
63 LegacySyntax,
64 Modules,
65 ClassSemantics,
66 EnumSemantics,
67 DeclarationMerging,
68 JavaScriptCompatibility,
69 Opinionated,
70 ControlFlow,
71}
72
73impl RuleGroup {
74 pub const ALL: [Self; 11] = [
75 Self::Unsoundness,
76 Self::EscapeHatches,
77 Self::NonErasable,
78 Self::LegacySyntax,
79 Self::Modules,
80 Self::ClassSemantics,
81 Self::EnumSemantics,
82 Self::DeclarationMerging,
83 Self::JavaScriptCompatibility,
84 Self::Opinionated,
85 Self::ControlFlow,
86 ];
87
88 #[must_use]
89 pub const fn slug(self) -> &'static str {
90 match self {
91 Self::Unsoundness => "unsoundness",
92 Self::EscapeHatches => "escape-hatches",
93 Self::NonErasable => "non-erasable",
94 Self::LegacySyntax => "legacy-syntax",
95 Self::Modules => "modules",
96 Self::ClassSemantics => "class-semantics",
97 Self::EnumSemantics => "enum-semantics",
98 Self::DeclarationMerging => "declaration-merging",
99 Self::JavaScriptCompatibility => "javascript-compatibility",
100 Self::Opinionated => "opinionated",
101 Self::ControlFlow => "control-flow",
102 }
103 }
104}
105
106impl FromStr for RuleGroup {
107 type Err = ParseRuleGroupError;
108
109 fn from_str(value: &str) -> Result<Self, Self::Err> {
110 Self::ALL
111 .into_iter()
112 .find(|group| group.slug() == value)
113 .ok_or_else(|| ParseRuleGroupError(Arc::from(value)))
114 }
115}
116
117#[derive(Clone, Debug, Eq, PartialEq)]
118pub struct ParseRuleGroupError(Arc<str>);
119
120impl fmt::Display for ParseRuleGroupError {
121 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
122 write!(formatter, "unknown lint group {:?}", self.0)
123 }
124}
125
126impl std::error::Error for ParseRuleGroupError {}
127
128#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
130pub struct RuleId {
131 code: &'static str,
132 slug: &'static str,
133}
134
135impl RuleId {
136 #[must_use]
137 pub const fn code(self) -> &'static str {
138 self.code
139 }
140
141 #[must_use]
142 pub const fn slug(self) -> &'static str {
143 self.slug
144 }
145}
146
147#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
150pub struct RuleExampleSource {
151 script_kind: ScriptKind,
152 text: &'static str,
153 resolves_to: Option<usize>,
154}
155
156impl RuleExampleSource {
157 #[must_use]
158 pub const fn new(script_kind: ScriptKind, text: &'static str) -> Self {
159 Self {
160 script_kind,
161 text,
162 resolves_to: None,
163 }
164 }
165
166 #[must_use]
167 pub const fn resolving_to(mut self, source: usize) -> Self {
168 self.resolves_to = Some(source);
169 self
170 }
171
172 #[must_use]
173 pub const fn script_kind(self) -> ScriptKind {
174 self.script_kind
175 }
176
177 #[must_use]
178 pub const fn text(self) -> &'static str {
179 self.text
180 }
181
182 #[must_use]
183 pub const fn resolves_to(self) -> Option<usize> {
184 self.resolves_to
185 }
186}
187
188#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
190pub struct CompilerLintOptions {
191 pub preserve_const_enums: bool,
192 pub emit_decorator_metadata: bool,
193 pub use_define_for_class_fields: bool,
194}
195
196impl CompilerLintOptions {
197 pub const STANDARD: Self = Self {
198 preserve_const_enums: false,
199 emit_decorator_metadata: false,
200 use_define_for_class_fields: true,
201 };
202}
203
204#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
206pub enum RuleExampleCase {
207 Source(RuleExampleSource),
208 Program(&'static [RuleExampleSource]),
209 CompilerOptions(CompilerLintOptions),
210}
211
212#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
214pub struct RuleExamples {
215 trigger: RuleExampleCase,
216 clean: RuleExampleCase,
217}
218
219impl RuleExamples {
220 #[must_use]
221 pub const fn new(trigger: RuleExampleCase, clean: RuleExampleCase) -> Self {
222 Self { trigger, clean }
223 }
224
225 #[must_use]
226 pub const fn trigger(self) -> RuleExampleCase {
227 self.trigger
228 }
229
230 #[must_use]
231 pub const fn clean(self) -> RuleExampleCase {
232 self.clean
233 }
234}
235
236#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
241pub struct RuleDefinition {
242 id: RuleId,
243 group: RuleGroup,
244 default_level: LintLevel,
245 rationale: &'static str,
246 sound_alternative: &'static str,
247 silence_flag: &'static str,
248 examples: RuleExamples,
249}
250
251impl RuleDefinition {
252 const fn new(
253 id: RuleId,
254 group: RuleGroup,
255 default_level: LintLevel,
256 rationale: &'static str,
257 sound_alternative: &'static str,
258 silence_flag: &'static str,
259 examples: RuleExamples,
260 ) -> Self {
261 Self {
262 id,
263 group,
264 default_level,
265 rationale,
266 sound_alternative,
267 silence_flag,
268 examples,
269 }
270 }
271
272 #[must_use]
273 pub const fn id(&self) -> RuleId {
274 self.id
275 }
276
277 #[must_use]
278 pub const fn code(&self) -> &'static str {
279 self.id.code()
280 }
281
282 #[must_use]
283 pub const fn slug(&self) -> &'static str {
284 self.id.slug()
285 }
286
287 #[must_use]
288 pub const fn group(&self) -> RuleGroup {
289 self.group
290 }
291
292 #[must_use]
293 pub const fn default_level(&self) -> LintLevel {
294 self.default_level
295 }
296
297 #[must_use]
298 pub const fn rationale(&self) -> &'static str {
299 self.rationale
300 }
301
302 #[must_use]
303 pub const fn sound_alternative(&self) -> &'static str {
304 self.sound_alternative
305 }
306
307 #[must_use]
308 pub const fn silence_flag(&self) -> &'static str {
309 self.silence_flag
310 }
311
312 #[must_use]
313 pub const fn examples(&self) -> RuleExamples {
314 self.examples
315 }
316}
317
318macro_rules! source_example {
319 ($kind:ident, $text:literal) => {
320 RuleExampleCase::Source(RuleExampleSource::new(ScriptKind::$kind, $text))
321 };
322}
323
324macro_rules! examples {
325 ($trigger:literal, $clean:literal) => {
326 RuleExamples::new(
327 source_example!(TypeScript, $trigger),
328 source_example!(TypeScript, $clean),
329 )
330 };
331 ($kind:ident, $trigger:literal, $clean:literal) => {
332 RuleExamples::new(
333 source_example!($kind, $trigger),
334 source_example!($kind, $clean),
335 )
336 };
337}
338
339macro_rules! rule {
340 ($code:literal, $slug:literal, $group:ident, $level:ident, $rationale:literal, $alternative:literal, $examples:expr) => {
341 RuleDefinition::new(
342 RuleId {
343 code: $code,
344 slug: $slug,
345 },
346 RuleGroup::$group,
347 LintLevel::$level,
348 $rationale,
349 $alternative,
350 concat!("-A ", $slug),
351 $examples,
352 )
353 };
354}
355
356pub static RULES: [RuleDefinition; 86] = [
358 rule!(
359 "BAMTS-W001",
360 "method-parameter-bivariance",
361 Unsoundness,
362 Warn,
363 "Method parameters are bivariant, so a narrower handler can receive an incompatible value.",
364 "Use a function-property callback with a contravariant parameter.",
365 examples!(
366 "interface H { handle(x: Dog): void }",
367 "const safe: number = 1;"
368 )
369 ),
370 rule!(
371 "BAMTS-W002",
372 "mutable-array-covariance",
373 Unsoundness,
374 Warn,
375 "Mutable arrays are covariant, so a widened alias can write the wrong element type.",
376 "Expose readonly arrays across type boundaries.",
377 examples!(
378 "const dogs: Dog[] = []; const animals: Animal[] = dogs;",
379 "const dogs: Animal[] = []; const animals: Animal[] = dogs;"
380 )
381 ),
382 rule!(
383 "BAMTS-W003",
384 "non-fresh-excess-property",
385 Unsoundness,
386 Warn,
387 "A non-fresh object can bypass excess-property checks and hide misspelled fields.",
388 "Validate the object at its construction boundary.",
389 examples!(
390 "const candidate = { keep: 1, extra: true }; const target: { keep: number } = candidate;",
391 "const target: { keep: number } = { keep: 1, extra: true };"
392 )
393 ),
394 rule!(
395 "BAMTS-W004",
396 "delete-required-property",
397 Unsoundness,
398 Warn,
399 "Deleting a required property breaks the declared object shape.",
400 "Model removability with an optional property or a separate value.",
401 examples!(
402 "const item: { required: number } = { required: 1 }; delete item.required;",
403 "const item: { optional?: number } = {}; delete item.optional;"
404 )
405 ),
406 rule!(
407 "BAMTS-W005",
408 "unchecked-catch-member",
409 Unsoundness,
410 Warn,
411 "A catch binding is untrusted until it is narrowed before member access.",
412 "Narrow the caught value with a runtime guard.",
413 examples!(
414 "try {} catch (error) { error.message; }",
415 "try {} catch (error) { if (error instanceof Error) error.message; }"
416 )
417 ),
418 rule!(
419 "BAMTS-W006",
420 "generic-any-downcast",
421 EscapeHatches,
422 Warn,
423 "Casting any through a generic return loses the proof required by every caller.",
424 "Validate the input and return a concrete checked type.",
425 examples!(
426 "function f<T>(x:any):T{return x as T}",
427 "const safe: number = 1;"
428 )
429 ),
430 rule!(
431 "BAMTS-W007",
432 "dynamic-tuple-index",
433 Unsoundness,
434 Warn,
435 "A dynamic tuple index can read beyond the tuple's known bounds.",
436 "Use a literal index or prove the index is in range.",
437 examples!(
438 "const pair: [string, number] = [\"a\", 1]; pair[index];",
439 "const pair: [string, number] = [\"a\", 1]; pair[1];"
440 )
441 ),
442 rule!(
443 "BAMTS-W008",
444 "unchecked-index-signature-read",
445 Unsoundness,
446 Warn,
447 "An index-signature read can be absent even when its value type excludes undefined.",
448 "Handle undefined after the lookup.",
449 examples!(
450 "interface D {[key: string]: number} declare const d:D; declare const k:string; const n=d[k];",
451 "const colors: Record<'red', number>={red:1}; const n=colors['red'];"
452 )
453 ),
454 rule!(
455 "BAMTS-W009",
456 "explicit-undefined-for-optional",
457 Unsoundness,
458 Warn,
459 "An optional property without undefined distinguishes absence from an explicit undefined value.",
460 "Omit the property or include undefined in its declared type.",
461 examples!(
462 "const o: {x?: number} = {x: undefined};",
463 "const safe: number = 1;"
464 )
465 ),
466 rule!(
467 "BAMTS-W010",
468 "detached-this-method",
469 Unsoundness,
470 Warn,
471 "Extracting a receiver-dependent method loses the this binding it requires.",
472 "Bind the method or call it through its receiver.",
473 examples!("const f = obj.method; f();", "const safe: number = 1;")
474 ),
475 rule!(
476 "BAMTS-W011",
477 "divergent-accessor-types",
478 Unsoundness,
479 Warn,
480 "Different getter and setter types hide an unsafe property boundary.",
481 "Use one compatible property type or an explicit conversion method.",
482 examples!(
483 "class C { get x(): number { return 1 } set x(v: string | number) {} }",
484 "const safe: number = 1;"
485 )
486 ),
487 rule!(
488 "BAMTS-W012",
489 "readonly-alias-mutation",
490 Unsoundness,
491 Warn,
492 "A writable alias can mutate data promised as readonly elsewhere.",
493 "Keep the mutable value private and expose a readonly view.",
494 examples!(
495 "const r: {readonly x:number}=m; m.x=2;",
496 "const safe: number = 1;"
497 )
498 ),
499 rule!(
500 "BAMTS-W013",
501 "fewer-callback-parameters",
502 Unsoundness,
503 Warn,
504 "A callback that accepts fewer parameters can silently discard required protocol data.",
505 "Declare the callback parameters you intentionally receive.",
506 examples!(
507 "const f: (x:number,y:string)=>void = () => {};",
508 "const safe: number = 1;"
509 )
510 ),
511 rule!(
512 "BAMTS-W014",
513 "value-returning-void-callback",
514 Unsoundness,
515 Warn,
516 "A value returned from a void callback is silently discarded.",
517 "Use a block body when the return value is intentionally ignored.",
518 examples!("const f: () => void = () => 42;", "const safe: number = 1;")
519 ),
520 rule!(
521 "BAMTS-W015",
522 "open-object-keys-assumption",
523 Unsoundness,
524 Warn,
525 "Object.keys does not prove that runtime keys are limited to keyof T.",
526 "Validate keys at runtime or work from a closed key list.",
527 examples!(
528 "const ks = Object.keys(x) as (keyof typeof x)[];",
529 "const safe: number = 1;"
530 )
531 ),
532 rule!(
533 "BAMTS-W016",
534 "index-signature-dot-access",
535 Unsoundness,
536 Warn,
537 "Dot access through an index signature hides that a property may be absent.",
538 "Use bracket access and handle the missing value.",
539 examples!(
540 "interface D {[key:string]: number} declare const d:D; d.username;",
541 "interface D {[key:string]: number} declare const d:D; d['username'];"
542 )
543 ),
544 rule!(
545 "BAMTS-W017",
546 "explicit-any",
547 EscapeHatches,
548 Warn,
549 "Explicit any disables type checking at the annotated boundary.",
550 "Use unknown and narrow it before use.",
551 examples!("let value: any;", "const safe: number = 1;")
552 ),
553 rule!(
554 "BAMTS-W018",
555 "implicit-any",
556 EscapeHatches,
557 Warn,
558 "An inferred any lets an untyped value flow without an explicit boundary.",
559 "Add an explicit checked type or unknown annotation.",
560 examples!("function f(x) { return x; }", "const safe: number = 1;")
561 ),
562 rule!(
563 "BAMTS-W019",
564 "unchecked-type-assertion",
565 EscapeHatches,
566 Warn,
567 "A type assertion claims a narrower type without runtime proof.",
568 "Narrow with a guard or validate with a decoder.",
569 examples!("const n = value as number;", "const safe: number = 1;")
570 ),
571 rule!(
572 "BAMTS-W020",
573 "double-assertion",
574 EscapeHatches,
575 Warn,
576 "A double assertion bypasses assignability through any or unknown.",
577 "Convert or validate the value at the boundary.",
578 examples!(
579 "const n = value as unknown as number;",
580 "const safe: number = 1;"
581 )
582 ),
583 rule!(
584 "BAMTS-W021",
585 "non-null-assertion",
586 EscapeHatches,
587 Warn,
588 "A non-null assertion erases a possible null or undefined value.",
589 "Narrow the value before accessing it.",
590 examples!("node!.textContent;", "const safe: number = 1;")
591 ),
592 rule!(
593 "BAMTS-W022",
594 "definite-assignment-assertion",
595 EscapeHatches,
596 Warn,
597 "A definite-assignment assertion skips proof that a field is initialized.",
598 "Initialize the field or assign it in every constructor path.",
599 examples!("class C { value!: string }", "const safe: number = 1;")
600 ),
601 rule!(
602 "BAMTS-W023",
603 "diagnostic-suppression-directive",
604 EscapeHatches,
605 Warn,
606 "A TypeScript diagnostic directive hides a compiler check instead of resolving it.",
607 "Fix the diagnostic or make the boundary explicit.",
608 examples!("// @ts-ignore", "const safe: number = 1;")
609 ),
610 rule!(
611 "BAMTS-W024",
612 "runtime-namespace",
613 NonErasable,
614 Warn,
615 "A value-bearing namespace requires runtime code instead of erasing as type syntax.",
616 "Use ES modules or an ambient namespace.",
617 examples!(
618 "namespace N { export const x = 1 }",
619 "const safe: number = 1;"
620 )
621 ),
622 rule!(
623 "BAMTS-W025",
624 "parameter-property",
625 NonErasable,
626 Warn,
627 "A parameter property synthesizes a field assignment during compilation.",
628 "Declare the field and assign the constructor parameter explicitly.",
629 examples!(
630 "class C { constructor(public x: number) {} }",
631 "const safe: number = 1;"
632 )
633 ),
634 rule!(
635 "BAMTS-W026",
636 "legacy-decorator-semantics",
637 LegacySyntax,
638 Warn,
639 "Legacy decorators have semantics that differ from standard ECMAScript decorators.",
640 "Use standard decorators or an explicit wrapper.",
641 examples!("@sealed class C {}", "const safe: number = 1;")
642 ),
643 rule!(
644 "BAMTS-W027",
645 "angle-bracket-assertion",
646 LegacySyntax,
647 Warn,
648 "Angle-bracket assertions are ambiguous with JSX syntax.",
649 "Use the `as T` assertion spelling.",
650 examples!("const n = <number>value;", "const safe: number = 1;")
651 ),
652 rule!(
653 "BAMTS-W028",
654 "declaration-inference-dependency",
655 LegacySyntax,
656 Warn,
657 "Declaration output that depends on cross-file inference is fragile and non-local.",
658 "Write an explicit exported type annotation.",
659 RuleExamples::new(
660 RuleExampleCase::Program(&[
661 RuleExampleSource::new(
662 ScriptKind::TypeScript,
663 "import { make } from './dep.js'; export const value = make();"
664 )
665 .resolving_to(1),
666 RuleExampleSource::new(
667 ScriptKind::TypeScript,
668 "export const make = (): number => 1;"
669 )
670 ]),
671 source_example!(TypeScript, "export const value: number = 1;")
672 )
673 ),
674 rule!(
675 "BAMTS-W029",
676 "jsx-transform-required",
677 LegacySyntax,
678 Warn,
679 "JSX requires a configured runtime transform and cannot simply be erased.",
680 "Configure a JSX runtime or use ordinary function calls.",
681 examples!(
682 TypeScriptReact,
683 "const el = <Widget value={1} />;",
684 "const safe = 1;"
685 )
686 ),
687 rule!(
688 "BAMTS-W030",
689 "import-export-equals",
690 Modules,
691 Warn,
692 "TypeScript import-equals and export-equals require target-specific module rewriting.",
693 "Use standard ESM import and export syntax.",
694 examples!("import fs = require(\"fs\");", "const safe: number = 1;")
695 ),
696 rule!(
697 "BAMTS-W031",
698 "type-imported-as-value",
699 Modules,
700 Warn,
701 "A type-only import emitted as a value import creates a runtime dependency.",
702 "Use `import type` for type-only symbols.",
703 RuleExamples::new(
704 RuleExampleCase::Program(&[
705 RuleExampleSource::new(
706 ScriptKind::TypeScript,
707 "import { User } from './types.js'; const user: User = { name: 'Ada' };"
708 )
709 .resolving_to(1),
710 RuleExampleSource::new(
711 ScriptKind::TypeScript,
712 "export interface User { name: string }"
713 )
714 ]),
715 RuleExampleCase::Program(&[
716 RuleExampleSource::new(
717 ScriptKind::TypeScript,
718 "import type { User } from './types.js'; const user: User = { name: 'Ada' };"
719 )
720 .resolving_to(1),
721 RuleExampleSource::new(
722 ScriptKind::TypeScript,
723 "export interface User { name: string }"
724 )
725 ])
726 )
727 ),
728 rule!(
729 "BAMTS-W032",
730 "type-reexported-as-value",
731 Modules,
732 Warn,
733 "A type-only re-export emitted as a value re-export creates a runtime dependency.",
734 "Use `export type` for type-only symbols.",
735 RuleExamples::new(
736 RuleExampleCase::Program(&[
737 RuleExampleSource::new(
738 ScriptKind::TypeScript,
739 "export { User } from './types.js';"
740 )
741 .resolving_to(1),
742 RuleExampleSource::new(
743 ScriptKind::TypeScript,
744 "export interface User { name: string }"
745 )
746 ]),
747 RuleExampleCase::Program(&[
748 RuleExampleSource::new(
749 ScriptKind::TypeScript,
750 "export type { User } from './types.js';"
751 )
752 .resolving_to(1),
753 RuleExampleSource::new(
754 ScriptKind::TypeScript,
755 "export interface User { name: string }"
756 )
757 ])
758 )
759 ),
760 rule!(
761 "BAMTS-W033",
762 "commonjs-in-esm",
763 Modules,
764 Allow,
765 "CommonJS globals inside an ESM module depend on host-specific interop.",
766 "Use ESM exports or isolate the CommonJS bridge.",
767 examples!(
768 "export const x = require('x');",
769 "const x = require('x'); x;"
770 )
771 ),
772 rule!(
773 "BAMTS-W034",
774 "implicit-script-file",
775 Modules,
776 Allow,
777 "A file without imports or exports silently becomes a global script.",
778 "Add an explicit export or force module detection.",
779 examples!("const shared = 1;", "export {}; const shared = 1;")
780 ),
781 rule!(
782 "BAMTS-W035",
783 "unchecked-side-effect-import",
784 Modules,
785 Warn,
786 "An unresolved side-effect import can conceal a missing runtime dependency.",
787 "Resolve the module or declare the host-provided virtual module.",
788 RuleExamples::new(
789 RuleExampleCase::Program(&[RuleExampleSource::new(
790 ScriptKind::TypeScript,
791 "import './missing.js';"
792 )]),
793 RuleExampleCase::Program(&[
794 RuleExampleSource::new(ScriptKind::TypeScript, "import './polyfill.js';")
795 .resolving_to(1),
796 RuleExampleSource::new(ScriptKind::JavaScript, "globalThis.ready = true;")
797 ])
798 )
799 ),
800 rule!(
801 "BAMTS-W036",
802 "extensionless-relative-import",
803 Modules,
804 Warn,
805 "Relative ESM imports need a runtime file extension in Node-style resolution.",
806 "Write the explicit runtime extension.",
807 examples!("import {x} from \"./util\";", "const safe: number = 1;")
808 ),
809 rule!(
810 "BAMTS-W037",
811 "interop-dependent-default-import",
812 Modules,
813 Warn,
814 "A default import from CommonJS can rely on synthetic interop semantics.",
815 "Use a namespace import or a real ESM default export.",
816 RuleExamples::new(
817 RuleExampleCase::Program(&[
818 RuleExampleSource::new(
819 ScriptKind::TypeScript,
820 "import legacy from './legacy.js'; legacy();"
821 )
822 .resolving_to(1),
823 RuleExampleSource::new(
824 ScriptKind::JavaScript,
825 "module.exports = function legacy() {};"
826 )
827 ]),
828 RuleExampleCase::Program(&[
829 RuleExampleSource::new(
830 ScriptKind::TypeScript,
831 "import modern from './modern.js'; modern();"
832 )
833 .resolving_to(1),
834 RuleExampleSource::new(
835 ScriptKind::JavaScript,
836 "export default function modern() {}"
837 )
838 ])
839 )
840 ),
841 rule!(
842 "BAMTS-W038",
843 "virtual-call-in-constructor",
844 ClassSemantics,
845 Allow,
846 "A constructor dispatching to an overridable method can observe uninitialized derived state.",
847 "Defer the hook until construction is complete.",
848 examples!(
849 "class B { constructor(){ this.init() } }",
850 "const safe: number = 1;"
851 )
852 ),
853 rule!(
854 "BAMTS-W039",
855 "uninitialized-field-emit-split",
856 ClassSemantics,
857 Allow,
858 "An uninitialized field has different runtime presence under competing emit modes.",
859 "Initialize it or use `declare` when no own field is intended.",
860 examples!("class C { value: string; }", "const safe: number = 1;")
861 ),
862 rule!(
863 "BAMTS-W040",
864 "field-overrides-accessor",
865 ClassSemantics,
866 Allow,
867 "A defined field can shadow an inherited accessor instead of invoking it.",
868 "Use an accessor, `declare`, or a distinct field name.",
869 examples!(
870 "class B { get data():number{return 1} } class D extends B { data = 1; }",
871 "class B { get data():number{return 1} } class D extends B { declare data:number; }"
872 )
873 ),
874 rule!(
875 "BAMTS-W041",
876 "implicit-override",
877 ClassSemantics,
878 Allow,
879 "An unmarked override can silently drift when its base member changes.",
880 "Mark the member with `override`.",
881 examples!(
882 "class B { run(){} } class D extends B { run(){} }",
883 "class B { run(){} } class D extends B { override run(){} }"
884 )
885 ),
886 rule!(
887 "BAMTS-W042",
888 "typescript-private-field",
889 ClassSemantics,
890 Allow,
891 "A TypeScript private modifier erases and does not provide runtime privacy.",
892 "Use an ECMAScript `#private` field for runtime privacy.",
893 examples!("class C { private secret = 1 }", "const safe: number = 1;")
894 ),
895 rule!(
896 "BAMTS-W043",
897 "runtime-enum",
898 EnumSemantics,
899 Warn,
900 "A non-const enum creates a runtime object with non-erasable behavior.",
901 "Use a union or a const object when a runtime object is intentional.",
902 examples!("enum Color { Red, Blue }", "const safe: number = 1;")
903 ),
904 rule!(
905 "BAMTS-W044",
906 "const-enum",
907 EnumSemantics,
908 Warn,
909 "A const enum relies on compile-time inlining across compilation boundaries.",
910 "Use a union or a const object.",
911 examples!("const enum Code { Ok = 200 }", "const safe: number = 1;")
912 ),
913 rule!(
914 "BAMTS-W045",
915 "numeric-enum-number-flow",
916 EnumSemantics,
917 Warn,
918 "Numeric enums accept arbitrary numbers, weakening the enum boundary.",
919 "Use a string enum or validate the numeric value.",
920 examples!(
921 "enum E { A } let e:E=E.A; let n:number=e;",
922 "enum E { A } const e=E.A;"
923 )
924 ),
925 rule!(
926 "BAMTS-W046",
927 "heterogeneous-enum",
928 EnumSemantics,
929 Warn,
930 "A heterogeneous enum mixes unrelated representations and complicates consumers.",
931 "Use one representation or a discriminated union.",
932 examples!(
933 "enum Answer { No = 0, Yes = \"YES\" }",
934 "const safe: number = 1;"
935 )
936 ),
937 rule!(
938 "BAMTS-W047",
939 "computed-enum-member",
940 EnumSemantics,
941 Warn,
942 "A computed enum member depends on runtime evaluation rather than a stable constant.",
943 "Use a constant initializer or a separate runtime value.",
944 examples!("enum E { X = getValue() }", "const safe: number = 1;")
945 ),
946 rule!(
947 "BAMTS-W048",
948 "numeric-enum-reverse-lookup",
949 EnumSemantics,
950 Warn,
951 "Numeric enum reverse lookup depends on generated runtime mappings.",
952 "Store the display name explicitly.",
953 examples!(
954 "enum E { A } const name=E[E.A];",
955 "enum E { A } const value=E.A;"
956 )
957 ),
958 rule!(
959 "BAMTS-W049",
960 "interface-declaration-merge",
961 DeclarationMerging,
962 Warn,
963 "Same-scope interfaces merge implicitly, making a type's shape non-local.",
964 "Declare one complete interface or use a closed type alias.",
965 examples!(
966 "interface Box {x:number} interface Box {y:number}",
967 "const safe: number = 1;"
968 )
969 ),
970 rule!(
971 "BAMTS-W050",
972 "namespace-value-merge",
973 DeclarationMerging,
974 Warn,
975 "A namespace merged with a value creates an implicit hybrid declaration.",
976 "Use an explicit object or separate module export.",
977 examples!(
978 "function f(){} namespace f { export const x=1 }",
979 "const safe: number = 1;"
980 )
981 ),
982 rule!(
983 "BAMTS-W051",
984 "global-augmentation",
985 DeclarationMerging,
986 Warn,
987 "A global augmentation mutates ambient types for unrelated code.",
988 "Expose a local wrapper or explicit global installation boundary.",
989 examples!(
990 "declare global { interface Window { x: number } }",
991 "const safe: number = 1;"
992 )
993 ),
994 rule!(
995 "BAMTS-W052",
996 "module-augmentation",
997 DeclarationMerging,
998 Warn,
999 "A module augmentation changes another module's contract outside that module.",
1000 "Wrap or extend the module through an explicit local API.",
1001 examples!(
1002 "declare module \"lib\" { interface X { y: number } }",
1003 "const safe: number = 1;"
1004 )
1005 ),
1006 rule!(
1007 "BAMTS-W053",
1008 "ambient-value-declaration",
1009 DeclarationMerging,
1010 Warn,
1011 "An ambient value declaration cannot prove that the runtime provides the value.",
1012 "Pass the value explicitly or install it through a checked host API.",
1013 examples!("declare const injected: string;", "const safe: number = 1;")
1014 ),
1015 rule!(
1016 "BAMTS-W054",
1017 "javascript-input",
1018 JavaScriptCompatibility,
1019 Allow,
1020 "JavaScript source enters a typed program with weaker static guarantees.",
1021 "Convert the source to TypeScript or isolate it behind typed declarations.",
1022 RuleExamples::new(
1023 source_example!(JavaScript, "const legacy = 1;"),
1024 source_example!(TypeScript, "const safe: number = 1;")
1025 )
1026 ),
1027 rule!(
1028 "BAMTS-W055",
1029 "jsdoc-type-syntax",
1030 JavaScriptCompatibility,
1031 Allow,
1032 "JSDoc types make JavaScript comments carry part of the type system.",
1033 "Move the file to TypeScript with native type syntax.",
1034 examples!(
1035 JavaScript,
1036 "/** @type {number} */ let n = 1;",
1037 "const safe = 1;"
1038 )
1039 ),
1040 rule!(
1041 "BAMTS-W056",
1042 "prototype-class-pattern",
1043 JavaScriptCompatibility,
1044 Allow,
1045 "Prototype assignment spreads class behavior across mutable runtime objects.",
1046 "Use class syntax or an explicit factory object.",
1047 examples!(
1048 JavaScript,
1049 "Ctor.prototype.run = function() {};",
1050 "const safe = 1;"
1051 )
1052 ),
1053 rule!(
1054 "BAMTS-W057",
1055 "ts-check-directive",
1056 JavaScriptCompatibility,
1057 Allow,
1058 "A per-file ts-check directive makes type-checking policy non-uniform.",
1059 "Use project-wide checkJs or convert the file to TypeScript.",
1060 examples!(JavaScript, "// @ts-check", "const safe = 1;")
1061 ),
1062 rule!(
1063 "BAMTS-W058",
1064 "prefer-type-alias",
1065 Opinionated,
1066 Allow,
1067 "An interface can merge later, leaving an API shape open unintentionally.",
1068 "Use a type alias for a closed shape.",
1069 examples!("interface Point { x: number }", "const safe: number = 1;")
1070 ),
1071 rule!(
1072 "BAMTS-W059",
1073 "prefer-readonly-array",
1074 Opinionated,
1075 Allow,
1076 "A mutable array type advertises mutation where a read-only view may suffice.",
1077 "Accept `readonly T[]` unless mutation is required.",
1078 examples!("function f(xs: string[]) {}", "const safe: number = 1;")
1079 ),
1080 rule!(
1081 "BAMTS-W060",
1082 "prefer-function-property",
1083 Opinionated,
1084 Allow,
1085 "A method signature keeps bivariant parameter checking.",
1086 "Use a function-property signature for callback members.",
1087 examples!(
1088 "interface H { run(x: Animal): void }",
1089 "const safe: number = 1;"
1090 )
1091 ),
1092 rule!(
1093 "BAMTS-W061",
1094 "no-barrel-star-export",
1095 Opinionated,
1096 Allow,
1097 "A wildcard barrel export obscures the package's public dependency surface.",
1098 "Re-export the intended names explicitly.",
1099 examples!(
1100 "export * from \"./internal.js\";",
1101 "const safe: number = 1;"
1102 )
1103 ),
1104 rule!(
1105 "BAMTS-W062",
1106 "no-default-export",
1107 Opinionated,
1108 Allow,
1109 "A default export lets importers rename one public binding arbitrarily.",
1110 "Use a named export.",
1111 examples!(
1112 "export default function run() {}",
1113 "const safe: number = 1;"
1114 )
1115 ),
1116 rule!(
1117 "BAMTS-W063",
1118 "exhaustive-discriminated-switch",
1119 Opinionated,
1120 Allow,
1121 "A discriminated-union switch omits a reachable variant.",
1122 "Handle every variant and assert never in the default branch.",
1123 examples!(
1124 "type S = { kind: \"a\" } | { kind: \"b\" }; function f(s: S) { switch (s.kind) { case \"a\": break; } }",
1125 "const safe: number = 1;"
1126 )
1127 ),
1128 rule!(
1129 "BAMTS-W064",
1130 "long-parameter-list",
1131 Opinionated,
1132 Allow,
1133 "A long positional parameter list makes calls easy to misorder.",
1134 "Use a parameter object or smaller cohesive functions.",
1135 examples!(
1136 "function f(a:number,b:number,c:number,d:number,e:number) {}",
1137 "const safe: number = 1;"
1138 )
1139 ),
1140 rule!(
1141 "BAMTS-W065",
1142 "implicit-return-path",
1143 ControlFlow,
1144 Warn,
1145 "A function can complete without returning the value its signature implies.",
1146 "Return on every reachable path or include undefined in the return type.",
1147 examples!(
1148 "function f(x:boolean){ if(x)return 1 }",
1149 "function f(x:boolean){ if(x)return 0; try { return 1; } catch { return 2; } }"
1150 )
1151 ),
1152 rule!(
1153 "BAMTS-W066",
1154 "switch-fallthrough",
1155 ControlFlow,
1156 Warn,
1157 "A non-empty switch case falls through without an explicit transfer.",
1158 "Add break, return, throw, or an explicit fallthrough marker.",
1159 examples!(
1160 "switch(x){case 1: work(); case 2: stop();}",
1161 "const safe: number = 1;"
1162 )
1163 ),
1164 rule!(
1165 "BAMTS-W067",
1166 "unreachable-code",
1167 ControlFlow,
1168 Warn,
1169 "A statement is unreachable under the program's control flow.",
1170 "Remove it or restructure the surrounding control flow.",
1171 examples!("function f(){ return; work(); }", "const safe: number = 1;")
1172 ),
1173 rule!(
1174 "BAMTS-W068",
1175 "unused-label",
1176 ControlFlow,
1177 Warn,
1178 "A label is declared but never targeted, obscuring control flow.",
1179 "Remove the label or add its intended labeled transfer.",
1180 examples!(
1181 "unused: for (;;) { break; }",
1182 "outer: for (;;) { break outer; }"
1183 )
1184 ),
1185 rule!(
1186 "BAMTS-W069",
1187 "unused-local",
1188 ControlFlow,
1189 Warn,
1190 "A local binding is never read after declaration.",
1191 "Remove it or use it deliberately.",
1192 examples!(
1193 "function f(){ const x=1; }",
1194 "function f(){ const x=1; return x; }"
1195 )
1196 ),
1197 rule!(
1198 "BAMTS-W070",
1199 "unused-parameter",
1200 ControlFlow,
1201 Warn,
1202 "A declared parameter is never read by its function.",
1203 "Remove it or name an intentionally unused protocol parameter clearly.",
1204 examples!(
1205 "function f(unused: number) {}",
1206 "function f(used: number) { return used; }"
1207 )
1208 ),
1209 rule!(
1210 "BAMTS-W071",
1211 "invalid-number-formatting-options",
1212 Unsoundness,
1213 Warn,
1214 "Known number-formatting arguments lie outside the ECMAScript-supported range.",
1215 "Validate or clamp the argument before calling the method.",
1216 examples!("(42).toString(1);", "const safe: number = 1;")
1217 ),
1218 rule!(
1219 "BAMTS-W072",
1220 "unsound-numeric-key-order-assumption",
1221 Unsoundness,
1222 Warn,
1223 "Integer-like object keys are ordered before other keys, not purely by insertion.",
1224 "Avoid insertion-order dependence or sort the keys explicitly.",
1225 examples!("Object.keys({b: 1, \"2\": 2});", "const safe: number = 1;")
1226 ),
1227 rule!(
1228 "BAMTS-W073",
1229 "json-stringify-unserializable-type",
1230 Unsoundness,
1231 Warn,
1232 "JSON.stringify can throw for BigInt or return undefined for a top-level value.",
1233 "Validate serializability and handle the undefined result.",
1234 examples!("JSON.stringify(10n);", "const safe: number = 1;")
1235 ),
1236 rule!(
1237 "BAMTS-W074",
1238 "unchecked-json-parse-any",
1239 Unsoundness,
1240 Warn,
1241 "JSON.parse returns untrusted data that is consumed as a trusted type.",
1242 "Parse to unknown and validate with a decoder.",
1243 examples!(
1244 "const u: User = JSON.parse(text);",
1245 "const safe: number = 1;"
1246 )
1247 ),
1248 rule!(
1249 "BAMTS-W075",
1250 "numeric-array-default-sort",
1251 Unsoundness,
1252 Warn,
1253 "Comparator-free sort coerces elements to strings rather than numeric order.",
1254 "Pass an explicit numeric or domain comparator.",
1255 examples!("[10, 2, 5].sort();", "const safe: number = 1;")
1256 ),
1257 rule!(
1258 "BAMTS-W076",
1259 "loose-equality-coercion",
1260 Unsoundness,
1261 Warn,
1262 "Loose equality can depend on implicit abstract coercion.",
1263 "Use strict equality or an explicit conversion.",
1264 examples!("\"0\" == false;", "const safe: number = 1;")
1265 ),
1266 rule!(
1267 "BAMTS-W077",
1268 "object-implicit-toprimitive-coercion",
1269 Unsoundness,
1270 Warn,
1271 "Implicit object-to-primitive conversion can call surprising coercion hooks.",
1272 "Call String, Number, or an explicit conversion method.",
1273 examples!("\"key_\" + Object.create(null);", "const safe: number = 1;")
1274 ),
1275 rule!(
1276 "BAMTS-W078",
1277 "symbol-template-interpolation-throw",
1278 Unsoundness,
1279 Warn,
1280 "Interpolating a symbol directly into a template literal throws.",
1281 "Wrap it with String or use its description.",
1282 examples!("`ID: ${Symbol(\"x\")}`", "const safe: number = 1;")
1283 ),
1284 rule!(
1285 "BAMTS-W079",
1286 "nan-strict-comparison",
1287 Unsoundness,
1288 Warn,
1289 "NaN is never strictly equal to itself, so a direct comparison is ineffective.",
1290 "Use Number.isNaN.",
1291 examples!("if (value === NaN) {}", "const safe: number = 1;")
1292 ),
1293 rule!(
1294 "BAMTS-W080",
1295 "unsafe-tostringtag-override",
1296 Unsoundness,
1297 Warn,
1298 "A toStringTag override is not a trustworthy runtime brand.",
1299 "Use a string tag and validate the actual value shape.",
1300 examples!(
1301 "({ [Symbol.toStringTag]: 123 });",
1302 "const safe: number = 1;"
1303 )
1304 ),
1305 rule!(
1306 "BAMTS-W081",
1307 "uninitialized-class-field-shadowing",
1308 ClassSemantics,
1309 Allow,
1310 "An uninitialized derived field defines an own property that shadows an inherited accessor.",
1311 "Use `declare`, initialize deliberately, or rename the field.",
1312 examples!(
1313 "class B { get data():number{return 1} } class D extends B { data:number; }",
1314 "class B { get data():number{return 1} } class D extends B { declare data:number; }"
1315 )
1316 ),
1317 rule!(
1318 "BAMTS-W082",
1319 "preserve-const-enums-option",
1320 NonErasable,
1321 Warn,
1322 "Preserving const enums retains runtime enum objects while inlining their uses.",
1323 "Disable preserveConstEnums or replace the enum.",
1324 RuleExamples::new(
1325 RuleExampleCase::CompilerOptions(CompilerLintOptions {
1326 preserve_const_enums: true,
1327 ..CompilerLintOptions::STANDARD
1328 }),
1329 RuleExampleCase::CompilerOptions(CompilerLintOptions::STANDARD)
1330 )
1331 ),
1332 rule!(
1333 "BAMTS-W083",
1334 "emit-decorator-metadata-option",
1335 LegacySyntax,
1336 Warn,
1337 "Emitted decorator metadata couples runtime reflection to compiler type information.",
1338 "Disable metadata emit and provide explicit metadata.",
1339 RuleExamples::new(
1340 RuleExampleCase::CompilerOptions(CompilerLintOptions {
1341 emit_decorator_metadata: true,
1342 ..CompilerLintOptions::STANDARD
1343 }),
1344 RuleExampleCase::CompilerOptions(CompilerLintOptions::STANDARD)
1345 )
1346 ),
1347 rule!(
1348 "BAMTS-W084",
1349 "legacy-class-field-set-semantics",
1350 ClassSemantics,
1351 Allow,
1352 "Legacy class-field set semantics invoke inherited setters instead of defining fields.",
1353 "Enable standard define semantics.",
1354 RuleExamples::new(
1355 RuleExampleCase::CompilerOptions(CompilerLintOptions {
1356 use_define_for_class_fields: false,
1357 ..CompilerLintOptions::STANDARD
1358 }),
1359 RuleExampleCase::CompilerOptions(CompilerLintOptions::STANDARD)
1360 )
1361 ),
1362 rule!(
1363 "BAMTS-W085",
1364 "javascript-syntax-rejection",
1365 JavaScriptCompatibility,
1366 Deny,
1367 "TypeScript-only syntax in a JavaScript file violates that file's source dialect.",
1368 "Rename the file to TypeScript or remove the type syntax.",
1369 examples!(
1370 JavaScript,
1371 "interface Point { x: number }",
1372 "const safe = 1;"
1373 )
1374 ),
1375 rule!(
1376 "BAMTS-W086",
1377 "cjs-esm-named-export-mismatch",
1378 Modules,
1379 Warn,
1380 "An ESM named import from CommonJS may not exist in its statically detected exports.",
1381 "Use the CommonJS default export or a declared named export.",
1382 RuleExamples::new(
1383 RuleExampleCase::Program(&[
1384 RuleExampleSource::new(
1385 ScriptKind::TypeScript,
1386 "import { helper } from './legacy.js'; helper();"
1387 )
1388 .resolving_to(1),
1389 RuleExampleSource::new(ScriptKind::JavaScript, "exports.other = () => 1;")
1390 ]),
1391 RuleExampleCase::Program(&[
1392 RuleExampleSource::new(
1393 ScriptKind::TypeScript,
1394 "import { helper } from './legacy.js'; helper();"
1395 )
1396 .resolving_to(1),
1397 RuleExampleSource::new(
1398 ScriptKind::JavaScript,
1399 "function helper() {} module.exports = { helper };"
1400 )
1401 ])
1402 )
1403 ),
1404];
1405
1406#[must_use]
1408pub fn rule_reference() -> String {
1409 let mut reference = String::from(
1410 "# BamTS strictness rules\n\nThis file is generated from `bamts_compiler::lint::RULES`; do not edit it manually.\n",
1411 );
1412
1413 for rule in RULES {
1414 writeln!(
1415 reference,
1416 "\n## `{}`: `{}`\n\n- Group: `{}`\n- Default level: `{}`\n- Rationale: {}\n- Sound alternative: {}\n- Silence: `{}`\n- Trigger: {}\n- Clean: {}",
1417 rule.code(),
1418 rule.slug(),
1419 rule.group().slug(),
1420 rule.default_level(),
1421 rule.rationale(),
1422 rule.sound_alternative(),
1423 rule.silence_flag(),
1424 render_example(rule.examples().trigger()),
1425 render_example(rule.examples().clean()),
1426 )
1427 .expect("writing to a String cannot fail");
1428 }
1429
1430 reference
1431}
1432
1433fn render_example(example: RuleExampleCase) -> String {
1434 match example {
1435 RuleExampleCase::Source(source) => render_example_source(source),
1436 RuleExampleCase::Program(sources) => sources
1437 .iter()
1438 .map(|source| render_example_source(*source))
1439 .collect::<Vec<_>>()
1440 .join("<br>"),
1441 RuleExampleCase::CompilerOptions(options) => format!(
1442 "<code>preserveConstEnums={}, emitDecoratorMetadata={}, useDefineForClassFields={}</code>",
1443 options.preserve_const_enums,
1444 options.emit_decorator_metadata,
1445 options.use_define_for_class_fields,
1446 ),
1447 }
1448}
1449
1450fn render_example_source(source: RuleExampleSource) -> String {
1451 let escaped = source
1452 .text()
1453 .replace('&', "&")
1454 .replace('<', "<")
1455 .replace('>', ">");
1456 format!("<code>{:?}: {escaped}</code>", source.script_kind())
1457}
1458
1459#[must_use]
1460pub fn rule_by_code(code: &str) -> Option<&'static RuleDefinition> {
1461 RULES.iter().find(|rule| rule.code() == code)
1462}
1463
1464#[must_use]
1465pub fn rule_by_slug(slug: &str) -> Option<&'static RuleDefinition> {
1466 RULES.iter().find(|rule| rule.slug() == slug)
1467}
1468
1469#[must_use]
1470pub fn rule_by_name(name: &str) -> Option<&'static RuleDefinition> {
1471 rule_by_code(name)
1472 .or_else(|| rule_by_slug(name))
1473 .or_else(|| alias_by_name(name).and_then(|alias| rule_by_code(alias.target_code)))
1474}
1475
1476#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1477pub struct RuleAlias {
1478 alias: &'static str,
1479 target_code: &'static str,
1480}
1481
1482impl RuleAlias {
1483 #[must_use]
1484 pub const fn alias(self) -> &'static str {
1485 self.alias
1486 }
1487
1488 #[must_use]
1489 pub const fn target_code(self) -> &'static str {
1490 self.target_code
1491 }
1492}
1493
1494pub static RULE_ALIASES: [RuleAlias; 4] = [
1496 RuleAlias {
1497 alias: "any-downcast",
1498 target_code: "BAMTS-W006",
1499 },
1500 RuleAlias {
1501 alias: "excess-property-bypass",
1502 target_code: "BAMTS-W003",
1503 },
1504 RuleAlias {
1505 alias: "unchecked-catch-property-access",
1506 target_code: "BAMTS-W005",
1507 },
1508 RuleAlias {
1509 alias: "dynamic-tuple-out-of-bounds-indexing",
1510 target_code: "BAMTS-W007",
1511 },
1512];
1513
1514fn alias_by_name(name: &str) -> Option<RuleAlias> {
1515 RULE_ALIASES
1516 .iter()
1517 .copied()
1518 .find(|alias| alias.alias == name)
1519}
1520
1521#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1522pub struct RuleTombstone {
1523 code: &'static str,
1524}
1525
1526impl RuleTombstone {
1527 #[must_use]
1528 pub const fn code(self) -> &'static str {
1529 self.code
1530 }
1531}
1532
1533pub static RULE_TOMBSTONES: [RuleTombstone; 1] = [RuleTombstone { code: "BAMTS-W000" }];
1535
1536fn is_tombstone(name: &str) -> bool {
1537 RULE_TOMBSTONES.iter().any(|entry| entry.code == name)
1538}
1539
1540#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
1541pub enum LintProfile {
1542 #[default]
1543 Default,
1544 Strict,
1545 Pedantic,
1546}
1547
1548impl LintProfile {
1549 #[must_use]
1550 pub const fn as_str(self) -> &'static str {
1551 match self {
1552 Self::Default => "default",
1553 Self::Strict => "strict",
1554 Self::Pedantic => "pedantic",
1555 }
1556 }
1557
1558 fn level(self, rule: &RuleDefinition) -> LintLevel {
1559 let strict = match rule.group() {
1560 RuleGroup::Unsoundness
1561 | RuleGroup::EscapeHatches
1562 | RuleGroup::NonErasable
1563 | RuleGroup::LegacySyntax => LintLevel::Deny,
1564 RuleGroup::ClassSemantics => LintLevel::Warn,
1565 RuleGroup::JavaScriptCompatibility if rule.code() != "BAMTS-W085" => LintLevel::Warn,
1566 RuleGroup::EnumSemantics if rule.code() != "BAMTS-W044" => LintLevel::Deny,
1567 _ => rule.default_level(),
1568 };
1569 match self {
1570 Self::Default => rule.default_level(),
1571 Self::Strict => strict,
1572 Self::Pedantic => match rule.group() {
1573 RuleGroup::EscapeHatches => LintLevel::Forbid,
1574 RuleGroup::Opinionated => LintLevel::Warn,
1575 RuleGroup::ClassSemantics | RuleGroup::JavaScriptCompatibility => LintLevel::Deny,
1576 _ => strict,
1577 },
1578 }
1579 }
1580
1581 const fn unknown_level(self) -> LintLevel {
1582 match self {
1583 Self::Default => LintLevel::Warn,
1584 Self::Strict | Self::Pedantic => LintLevel::Deny,
1585 }
1586 }
1587}
1588
1589impl FromStr for LintProfile {
1590 type Err = ParseLintProfileError;
1591
1592 fn from_str(value: &str) -> Result<Self, Self::Err> {
1593 match value {
1594 "default" => Ok(Self::Default),
1595 "strict" => Ok(Self::Strict),
1596 "pedantic" => Ok(Self::Pedantic),
1597 _ => Err(ParseLintProfileError(Arc::from(value))),
1598 }
1599 }
1600}
1601
1602#[derive(Clone, Debug, Eq, PartialEq)]
1603pub struct ParseLintProfileError(Arc<str>);
1604
1605impl fmt::Display for ParseLintProfileError {
1606 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1607 write!(formatter, "unknown lint profile {:?}", self.0)
1608 }
1609}
1610
1611impl std::error::Error for ParseLintProfileError {}
1612
1613#[derive(Clone, Debug, Eq, PartialEq)]
1614pub struct LintSetting {
1615 name: Arc<str>,
1616 level: LintLevel,
1617 source: Arc<str>,
1618}
1619
1620impl LintSetting {
1621 #[must_use]
1622 pub fn new(name: impl Into<Arc<str>>, level: LintLevel, source: impl Into<Arc<str>>) -> Self {
1623 Self {
1624 name: name.into(),
1625 level,
1626 source: source.into(),
1627 }
1628 }
1629
1630 #[must_use]
1631 pub fn name(&self) -> &str {
1632 &self.name
1633 }
1634
1635 #[must_use]
1636 pub const fn level(&self) -> LintLevel {
1637 self.level
1638 }
1639
1640 #[must_use]
1641 pub fn source(&self) -> &str {
1642 &self.source
1643 }
1644}
1645
1646#[derive(Clone, Debug, Default, Eq, PartialEq)]
1648pub struct LintConfig {
1649 groups: Vec<LintSetting>,
1650 rules: Vec<LintSetting>,
1651}
1652
1653impl LintConfig {
1654 #[must_use]
1655 pub const fn new(groups: Vec<LintSetting>, rules: Vec<LintSetting>) -> Self {
1656 Self { groups, rules }
1657 }
1658
1659 #[must_use]
1660 pub fn groups(&self) -> &[LintSetting] {
1661 &self.groups
1662 }
1663
1664 #[must_use]
1665 pub fn rules(&self) -> &[LintSetting] {
1666 &self.rules
1667 }
1668}
1669
1670#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1671pub enum OverrideTargetKind {
1672 Group,
1673 Rule,
1674 Either,
1675}
1676
1677#[derive(Clone, Debug, Eq, PartialEq)]
1678pub struct LintOverride {
1679 setting: LintSetting,
1680 target_kind: OverrideTargetKind,
1681}
1682
1683impl LintOverride {
1684 #[must_use]
1685 pub fn new(name: impl Into<Arc<str>>, level: LintLevel, source: impl Into<Arc<str>>) -> Self {
1686 Self {
1687 setting: LintSetting::new(name, level, source),
1688 target_kind: OverrideTargetKind::Either,
1689 }
1690 }
1691
1692 #[must_use]
1693 pub fn group(group: RuleGroup, level: LintLevel, source: impl Into<Arc<str>>) -> Self {
1694 Self {
1695 setting: LintSetting::new(group.slug(), level, source),
1696 target_kind: OverrideTargetKind::Group,
1697 }
1698 }
1699
1700 #[must_use]
1701 pub fn rule(rule: RuleId, level: LintLevel, source: impl Into<Arc<str>>) -> Self {
1702 Self {
1703 setting: LintSetting::new(rule.code(), level, source),
1704 target_kind: OverrideTargetKind::Rule,
1705 }
1706 }
1707}
1708
1709#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd)]
1710enum Specificity {
1711 Profile,
1712 Group,
1713 Rule,
1714}
1715
1716#[derive(Clone, Debug, Eq, PartialEq)]
1717struct AppliedLevel {
1718 level: LintLevel,
1719 source: Arc<str>,
1720 specificity: Specificity,
1721}
1722
1723#[derive(Clone, Debug, Eq, PartialEq)]
1724struct RuleState {
1725 profile: AppliedLevel,
1726 group: Option<AppliedLevel>,
1727 rule: Option<AppliedLevel>,
1728}
1729
1730impl RuleState {
1731 fn effective(&self) -> &AppliedLevel {
1732 self.rule
1733 .as_ref()
1734 .or(self.group.as_ref())
1735 .unwrap_or(&self.profile)
1736 }
1737}
1738
1739#[derive(Clone, Debug, Eq, PartialEq)]
1740pub enum LintIssueKind {
1741 RenamedRule { canonical: &'static str },
1742 RetiredCode,
1743 UnknownName { suggestion: Option<Arc<str>> },
1744}
1745
1746#[derive(Clone, Debug, Eq, PartialEq)]
1747pub struct LintIssue {
1748 name: Arc<str>,
1749 level: LintLevel,
1750 source: Arc<str>,
1751 kind: LintIssueKind,
1752}
1753
1754impl LintIssue {
1755 #[must_use]
1756 pub fn name(&self) -> &str {
1757 &self.name
1758 }
1759
1760 #[must_use]
1761 pub const fn level(&self) -> LintLevel {
1762 self.level
1763 }
1764
1765 #[must_use]
1766 pub fn source(&self) -> &str {
1767 &self.source
1768 }
1769
1770 #[must_use]
1771 pub const fn kind(&self) -> &LintIssueKind {
1772 &self.kind
1773 }
1774}
1775
1776#[derive(Clone, Debug, Eq, PartialEq)]
1777pub struct ForbidOverrideError {
1778 rule: RuleId,
1779 forbidden_by: Arc<str>,
1780 lowered_by: Arc<str>,
1781}
1782
1783impl ForbidOverrideError {
1784 #[must_use]
1785 pub const fn rule(&self) -> RuleId {
1786 self.rule
1787 }
1788
1789 #[must_use]
1790 pub fn forbidden_by(&self) -> &str {
1791 &self.forbidden_by
1792 }
1793
1794 #[must_use]
1795 pub fn lowered_by(&self) -> &str {
1796 &self.lowered_by
1797 }
1798}
1799
1800impl fmt::Display for ForbidOverrideError {
1801 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1802 write!(
1803 formatter,
1804 "rule {} ({}) was forbidden by {}; {} cannot lower it",
1805 self.rule.code(),
1806 self.rule.slug(),
1807 self.forbidden_by,
1808 self.lowered_by
1809 )
1810 }
1811}
1812
1813impl std::error::Error for ForbidOverrideError {}
1814
1815#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1816pub enum SourceDialect {
1817 TypeScript,
1818 JavaScript,
1819}
1820
1821#[derive(Clone, Debug, Eq, PartialEq)]
1823pub struct LintTable {
1824 profile: LintProfile,
1825 states: Vec<RuleState>,
1826}
1827
1828impl LintTable {
1829 #[must_use]
1830 pub fn new(profile: LintProfile) -> Self {
1831 let profile_source: Arc<str> = Arc::from(format!("{} profile", profile.as_str()));
1832 let states = RULES
1833 .iter()
1834 .map(|rule| RuleState {
1835 profile: AppliedLevel {
1836 level: profile.level(rule),
1837 source: Arc::clone(&profile_source),
1838 specificity: Specificity::Profile,
1839 },
1840 group: None,
1841 rule: None,
1842 })
1843 .collect();
1844 Self { profile, states }
1845 }
1846
1847 #[must_use]
1848 pub const fn profile(&self) -> LintProfile {
1849 self.profile
1850 }
1851
1852 #[must_use]
1853 pub fn level(&self, rule: RuleId) -> LintLevel {
1854 self.state(rule).effective().level
1855 }
1856
1857 #[must_use]
1858 pub fn source(&self, rule: RuleId) -> &str {
1859 &self.state(rule).effective().source
1860 }
1861
1862 #[must_use]
1864 pub fn level_for_source(&self, rule: RuleId, dialect: SourceDialect) -> LintLevel {
1865 if dialect == SourceDialect::TypeScript {
1866 return self.level(rule);
1867 }
1868 let spec_footgun = matches!(
1869 rule.code(),
1870 "BAMTS-W071"
1871 | "BAMTS-W072"
1872 | "BAMTS-W073"
1873 | "BAMTS-W074"
1874 | "BAMTS-W075"
1875 | "BAMTS-W076"
1876 | "BAMTS-W077"
1877 | "BAMTS-W078"
1878 | "BAMTS-W079"
1879 | "BAMTS-W080"
1880 );
1881 let control_flow = RULES[rule_index(rule)].group() == RuleGroup::ControlFlow;
1882 let javascript_compatibility =
1883 RULES[rule_index(rule)].group() == RuleGroup::JavaScriptCompatibility;
1884 if spec_footgun || control_flow || javascript_compatibility {
1885 let effective = self.level(rule);
1886 return if effective == LintLevel::Allow {
1887 LintLevel::Allow
1888 } else {
1889 LintLevel::Warn
1890 };
1891 }
1892 LintLevel::Allow
1893 }
1894
1895 pub fn apply_config(
1897 &mut self,
1898 config: &LintConfig,
1899 ) -> Result<Vec<LintIssue>, ForbidOverrideError> {
1900 let mut issues = Vec::new();
1901 for setting in config.groups() {
1902 self.apply_setting(setting, OverrideTargetKind::Group, &mut issues)?;
1903 }
1904 for setting in config.rules() {
1905 self.apply_setting(setting, OverrideTargetKind::Rule, &mut issues)?;
1906 }
1907 Ok(issues)
1908 }
1909
1910 pub fn apply_cli(
1912 &mut self,
1913 overrides: impl IntoIterator<Item = LintOverride>,
1914 ) -> Result<Vec<LintIssue>, ForbidOverrideError> {
1915 let mut issues = Vec::new();
1916 for lint_override in overrides {
1917 self.apply_setting(
1918 &lint_override.setting,
1919 lint_override.target_kind,
1920 &mut issues,
1921 )?;
1922 }
1923 Ok(issues)
1924 }
1925
1926 fn apply_setting(
1927 &mut self,
1928 setting: &LintSetting,
1929 kind: OverrideTargetKind,
1930 issues: &mut Vec<LintIssue>,
1931 ) -> Result<(), ForbidOverrideError> {
1932 if kind != OverrideTargetKind::Rule {
1933 if let Ok(group) = RuleGroup::from_str(setting.name()) {
1934 return self.apply_group(group, setting);
1935 }
1936 if kind == OverrideTargetKind::Group {
1937 issues.push(self.unknown_issue(setting, group_suggestion(setting.name())));
1938 return Ok(());
1939 }
1940 }
1941
1942 if is_tombstone(setting.name()) {
1943 issues.push(LintIssue {
1944 name: Arc::clone(&setting.name),
1945 level: LintLevel::Deny,
1946 source: Arc::clone(&setting.source),
1947 kind: LintIssueKind::RetiredCode,
1948 });
1949 return Ok(());
1950 }
1951 if let Some(rule) = rule_by_code(setting.name()).or_else(|| rule_by_slug(setting.name())) {
1952 return self.apply_rule(rule, setting);
1953 }
1954 if let Some(alias) = alias_by_name(setting.name()) {
1955 let rule = rule_by_code(alias.target_code).expect("alias target must be registered");
1956 issues.push(LintIssue {
1957 name: Arc::clone(&setting.name),
1958 level: LintLevel::Warn,
1959 source: Arc::clone(&setting.source),
1960 kind: LintIssueKind::RenamedRule {
1961 canonical: rule.slug(),
1962 },
1963 });
1964 return self.apply_rule(rule, setting);
1965 }
1966 issues.push(self.unknown_issue(setting, rule_suggestion(setting.name())));
1967 Ok(())
1968 }
1969
1970 fn apply_group(
1971 &mut self,
1972 group: RuleGroup,
1973 setting: &LintSetting,
1974 ) -> Result<(), ForbidOverrideError> {
1975 let targets: Vec<usize> = RULES
1976 .iter()
1977 .enumerate()
1978 .filter_map(|(index, rule)| (rule.group() == group).then_some(index))
1979 .collect();
1980 self.check_forbid(&targets, setting, Specificity::Group)?;
1981 for index in targets {
1982 self.states[index].group = Some(AppliedLevel {
1983 level: setting.level,
1984 source: Arc::clone(&setting.source),
1985 specificity: Specificity::Group,
1986 });
1987 }
1988 Ok(())
1989 }
1990
1991 fn apply_rule(
1992 &mut self,
1993 rule: &'static RuleDefinition,
1994 setting: &LintSetting,
1995 ) -> Result<(), ForbidOverrideError> {
1996 let index = rule_index(rule.id());
1997 self.check_forbid(&[index], setting, Specificity::Rule)?;
1998 self.states[index].rule = Some(AppliedLevel {
1999 level: setting.level,
2000 source: Arc::clone(&setting.source),
2001 specificity: Specificity::Rule,
2002 });
2003 Ok(())
2004 }
2005
2006 fn check_forbid(
2007 &self,
2008 indices: &[usize],
2009 setting: &LintSetting,
2010 specificity: Specificity,
2011 ) -> Result<(), ForbidOverrideError> {
2012 if setting.level == LintLevel::Forbid {
2013 return Ok(());
2014 }
2015 for &index in indices {
2016 let active = self.states[index].effective();
2017 if active.level == LintLevel::Forbid && specificity >= active.specificity {
2018 return Err(ForbidOverrideError {
2019 rule: RULES[index].id(),
2020 forbidden_by: Arc::clone(&active.source),
2021 lowered_by: Arc::clone(&setting.source),
2022 });
2023 }
2024 }
2025 Ok(())
2026 }
2027
2028 fn unknown_issue(&self, setting: &LintSetting, suggestion: Option<Arc<str>>) -> LintIssue {
2029 LintIssue {
2030 name: Arc::clone(&setting.name),
2031 level: self.profile.unknown_level(),
2032 source: Arc::clone(&setting.source),
2033 kind: LintIssueKind::UnknownName { suggestion },
2034 }
2035 }
2036
2037 fn state(&self, rule: RuleId) -> &RuleState {
2038 &self.states[rule_index(rule)]
2039 }
2040}
2041
2042fn rule_index(id: RuleId) -> usize {
2043 let code = id.code().as_bytes();
2044 let number = usize::from(code[7] - b'0') * 100
2045 + usize::from(code[8] - b'0') * 10
2046 + usize::from(code[9] - b'0');
2047 number - 1
2048}
2049
2050fn group_suggestion(name: &str) -> Option<Arc<str>> {
2051 nearest_name(name, RuleGroup::ALL.into_iter().map(RuleGroup::slug))
2052}
2053
2054fn rule_suggestion(name: &str) -> Option<Arc<str>> {
2055 nearest_name(
2056 name,
2057 RULES
2058 .iter()
2059 .flat_map(|rule| [rule.code(), rule.slug()])
2060 .chain(RULE_ALIASES.iter().map(|alias| alias.alias)),
2061 )
2062}
2063
2064fn nearest_name<'a>(name: &str, candidates: impl Iterator<Item = &'a str>) -> Option<Arc<str>> {
2065 candidates
2066 .map(|candidate| (levenshtein(name, candidate), candidate))
2067 .min_by_key(|(distance, candidate)| (*distance, *candidate))
2068 .map(|(_, candidate)| Arc::from(candidate))
2069}
2070
2071fn levenshtein(left: &str, right: &str) -> usize {
2072 let right_chars: Vec<char> = right.chars().collect();
2073 let mut previous: Vec<usize> = (0..=right_chars.len()).collect();
2074 let mut current = vec![0; right_chars.len() + 1];
2075 for (left_index, left_char) in left.chars().enumerate() {
2076 current[0] = left_index + 1;
2077 for (right_index, right_char) in right_chars.iter().enumerate() {
2078 let substitution = previous[right_index] + usize::from(left_char != *right_char);
2079 current[right_index + 1] = (current[right_index] + 1)
2080 .min(previous[right_index + 1] + 1)
2081 .min(substitution);
2082 }
2083 std::mem::swap(&mut previous, &mut current);
2084 }
2085 previous[right_chars.len()]
2086}
2087
2088#[cfg(test)]
2089mod tests {
2090 use super::*;
2091
2092 fn rule(slug: &str) -> RuleId {
2093 rule_by_slug(slug).expect("test rule must exist").id()
2094 }
2095
2096 #[test]
2097 fn registry_is_complete_and_unique() {
2098 assert_eq!(RULES.len(), 86);
2099 for (index, rule) in RULES.iter().enumerate() {
2100 assert!(rule.code().starts_with("BAMTS-W"));
2101 assert!(!rule.slug().is_empty());
2102 assert!(
2103 !RULES[..index]
2104 .iter()
2105 .any(|other| other.code() == rule.code())
2106 );
2107 assert!(
2108 !RULES[..index]
2109 .iter()
2110 .any(|other| other.slug() == rule.slug())
2111 );
2112 assert!(
2113 !RULE_TOMBSTONES
2114 .iter()
2115 .any(|entry| entry.code() == rule.code())
2116 );
2117 }
2118 }
2119
2120 #[test]
2121 fn ordered_overrides_keep_rule_specificity_over_later_group() {
2122 let target = rule("explicit-any");
2123 let mut table = LintTable::new(LintProfile::Default);
2124 table
2125 .apply_cli([
2126 LintOverride::group(
2127 RuleGroup::EscapeHatches,
2128 LintLevel::Deny,
2129 "-D escape-hatches",
2130 ),
2131 LintOverride::rule(target, LintLevel::Allow, "-A explicit-any"),
2132 LintOverride::group(
2133 RuleGroup::EscapeHatches,
2134 LintLevel::Warn,
2135 "-W escape-hatches",
2136 ),
2137 ])
2138 .unwrap();
2139 assert_eq!(table.level(target), LintLevel::Allow);
2140 assert_eq!(table.source(target), "-A explicit-any");
2141 assert_eq!(table.level(rule("implicit-any")), LintLevel::Warn);
2142 }
2143
2144 #[test]
2145 fn later_override_wins_within_the_same_specificity() {
2146 let target = rule("unused-local");
2147 let mut table = LintTable::new(LintProfile::Default);
2148 table
2149 .apply_cli([
2150 LintOverride::rule(target, LintLevel::Deny, "first"),
2151 LintOverride::rule(target, LintLevel::Warn, "second"),
2152 ])
2153 .unwrap();
2154 assert_eq!(table.level(target), LintLevel::Warn);
2155 assert_eq!(table.source(target), "second");
2156 }
2157
2158 #[test]
2159 fn forbid_lock_reports_both_sources() {
2160 let target = rule("explicit-any");
2161 let mut table = LintTable::new(LintProfile::Default);
2162 table
2163 .apply_cli([LintOverride::rule(
2164 target,
2165 LintLevel::Forbid,
2166 "security policy",
2167 )])
2168 .unwrap();
2169 let error = table
2170 .apply_cli([LintOverride::rule(
2171 target,
2172 LintLevel::Warn,
2173 "developer flag",
2174 )])
2175 .unwrap_err();
2176 assert_eq!(error.rule(), target);
2177 assert_eq!(error.forbidden_by(), "security policy");
2178 assert_eq!(error.lowered_by(), "developer flag");
2179 }
2180
2181 #[test]
2182 fn profiles_expand_the_settled_families() {
2183 let escape = rule("explicit-any");
2184 let opinionated = rule("prefer-type-alias");
2185 let module_exception = rule("commonjs-in-esm");
2186 let const_enum = rule("const-enum");
2187 assert_eq!(
2188 LintTable::new(LintProfile::Default).level(escape),
2189 LintLevel::Warn
2190 );
2191 assert_eq!(
2192 LintTable::new(LintProfile::Strict).level(escape),
2193 LintLevel::Deny
2194 );
2195 assert_eq!(
2196 LintTable::new(LintProfile::Pedantic).level(escape),
2197 LintLevel::Forbid
2198 );
2199 assert_eq!(
2200 LintTable::new(LintProfile::Default).level(opinionated),
2201 LintLevel::Allow
2202 );
2203 assert_eq!(
2204 LintTable::new(LintProfile::Strict).level(opinionated),
2205 LintLevel::Allow
2206 );
2207 assert_eq!(
2208 LintTable::new(LintProfile::Pedantic).level(opinionated),
2209 LintLevel::Warn
2210 );
2211 assert_eq!(
2212 LintTable::new(LintProfile::Strict).level(module_exception),
2213 LintLevel::Allow
2214 );
2215 assert_eq!(
2216 LintTable::new(LintProfile::Strict).level(const_enum),
2217 LintLevel::Warn
2218 );
2219 assert_eq!(
2220 LintTable::new(LintProfile::Strict).level(rule("runtime-enum")),
2221 LintLevel::Deny
2222 );
2223 }
2224
2225 #[test]
2226 fn aliases_resolve_and_warn_without_losing_the_setting() {
2227 let mut table = LintTable::new(LintProfile::Default);
2228 let issues = table
2229 .apply_cli([LintOverride::new(
2230 "any-downcast",
2231 LintLevel::Deny,
2232 "legacy config",
2233 )])
2234 .unwrap();
2235 assert_eq!(table.level(rule("generic-any-downcast")), LintLevel::Deny);
2236 assert!(matches!(
2237 issues[0].kind(),
2238 LintIssueKind::RenamedRule {
2239 canonical: "generic-any-downcast"
2240 }
2241 ));
2242 }
2243
2244 #[test]
2245 fn tombstones_are_rejected() {
2246 let mut table = LintTable::new(LintProfile::Default);
2247 let issues = table
2248 .apply_cli([LintOverride::new("BAMTS-W000", LintLevel::Warn, "config")])
2249 .unwrap();
2250 assert_eq!(issues[0].level(), LintLevel::Deny);
2251 assert_eq!(issues[0].kind(), &LintIssueKind::RetiredCode);
2252 }
2253
2254 #[test]
2255 fn unknown_names_warn_by_default_and_deny_in_stricter_profiles() {
2256 for (profile, level) in [
2257 (LintProfile::Default, LintLevel::Warn),
2258 (LintProfile::Strict, LintLevel::Deny),
2259 (LintProfile::Pedantic, LintLevel::Deny),
2260 ] {
2261 let mut table = LintTable::new(profile);
2262 let issues = table
2263 .apply_cli([LintOverride::new("explicit-ang", LintLevel::Warn, "config")])
2264 .unwrap();
2265 assert_eq!(issues[0].level(), level);
2266 assert!(matches!(
2267 issues[0].kind(),
2268 LintIssueKind::UnknownName { suggestion: Some(name) } if name.as_ref() == "explicit-any"
2269 ));
2270 }
2271 }
2272
2273 #[test]
2274 fn javascript_dialect_preserves_allow_and_clamps_enabled_rules_to_warning() {
2275 let table = LintTable::new(LintProfile::Pedantic);
2276 assert_eq!(
2277 table.level_for_source(
2278 rule("invalid-number-formatting-options"),
2279 SourceDialect::JavaScript
2280 ),
2281 LintLevel::Warn
2282 );
2283 assert_eq!(
2284 table.level_for_source(rule("unused-local"), SourceDialect::JavaScript),
2285 LintLevel::Warn
2286 );
2287 assert_eq!(
2288 table.level_for_source(rule("explicit-any"), SourceDialect::JavaScript),
2289 LintLevel::Allow
2290 );
2291 assert_eq!(
2292 table.level_for_source(
2293 rule("javascript-syntax-rejection"),
2294 SourceDialect::JavaScript
2295 ),
2296 LintLevel::Warn
2297 );
2298 assert_eq!(
2299 LintTable::new(LintProfile::Default)
2300 .level_for_source(rule("javascript-input"), SourceDialect::JavaScript,),
2301 LintLevel::Allow
2302 );
2303 }
2304}