@eddacraft/anvil-core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +14 -0
- package/dist/antipattern/index.d.ts +11 -0
- package/dist/antipattern/index.d.ts.map +1 -0
- package/dist/antipattern/index.js +31 -0
- package/dist/antipattern/patterns-css.d.ts +17 -0
- package/dist/antipattern/patterns-css.d.ts.map +1 -0
- package/dist/antipattern/patterns-css.js +72 -0
- package/dist/antipattern/patterns-html.d.ts +21 -0
- package/dist/antipattern/patterns-html.d.ts.map +1 -0
- package/dist/antipattern/patterns-html.js +139 -0
- package/dist/antipattern/patterns.d.ts +72 -0
- package/dist/antipattern/patterns.d.ts.map +1 -0
- package/dist/antipattern/patterns.js +301 -0
- package/dist/antipattern/scanner.d.ts +32 -0
- package/dist/antipattern/scanner.d.ts.map +1 -0
- package/dist/antipattern/scanner.js +89 -0
- package/dist/antipattern/types.d.ts +318 -0
- package/dist/antipattern/types.d.ts.map +1 -0
- package/dist/antipattern/types.js +278 -0
- package/dist/architecture/analyzer.d.ts +123 -0
- package/dist/architecture/analyzer.d.ts.map +1 -0
- package/dist/architecture/analyzer.js +321 -0
- package/dist/architecture/baseline.d.ts +112 -0
- package/dist/architecture/baseline.d.ts.map +1 -0
- package/dist/architecture/baseline.js +245 -0
- package/dist/architecture/compiler.d.ts +24 -0
- package/dist/architecture/compiler.d.ts.map +1 -0
- package/dist/architecture/compiler.js +57 -0
- package/dist/architecture/context.d.ts +129 -0
- package/dist/architecture/context.d.ts.map +1 -0
- package/dist/architecture/context.js +116 -0
- package/dist/architecture/dc-generator.d.ts +9 -0
- package/dist/architecture/dc-generator.d.ts.map +1 -0
- package/dist/architecture/dc-generator.js +220 -0
- package/dist/architecture/definition-schema.d.ts +128 -0
- package/dist/architecture/definition-schema.d.ts.map +1 -0
- package/dist/architecture/definition-schema.js +94 -0
- package/dist/architecture/edge-detector-html.d.ts +6 -0
- package/dist/architecture/edge-detector-html.d.ts.map +1 -0
- package/dist/architecture/edge-detector-html.js +5 -0
- package/dist/architecture/edge-detector-web.d.ts +32 -0
- package/dist/architecture/edge-detector-web.d.ts.map +1 -0
- package/dist/architecture/edge-detector-web.js +133 -0
- package/dist/architecture/edge-detector.d.ts +116 -0
- package/dist/architecture/edge-detector.d.ts.map +1 -0
- package/dist/architecture/edge-detector.js +229 -0
- package/dist/architecture/entry-detector.d.ts +44 -0
- package/dist/architecture/entry-detector.d.ts.map +1 -0
- package/dist/architecture/entry-detector.js +263 -0
- package/dist/architecture/index.d.ts +21 -0
- package/dist/architecture/index.d.ts.map +1 -0
- package/dist/architecture/index.js +48 -0
- package/dist/architecture/layer-detector.d.ts +60 -0
- package/dist/architecture/layer-detector.d.ts.map +1 -0
- package/dist/architecture/layer-detector.js +331 -0
- package/dist/architecture/rego-generator.d.ts +25 -0
- package/dist/architecture/rego-generator.d.ts.map +1 -0
- package/dist/architecture/rego-generator.js +229 -0
- package/dist/architecture/templates/index.d.ts +39 -0
- package/dist/architecture/templates/index.d.ts.map +1 -0
- package/dist/architecture/templates/index.js +124 -0
- package/dist/architecture/types.d.ts +280 -0
- package/dist/architecture/types.d.ts.map +1 -0
- package/dist/architecture/types.js +269 -0
- package/dist/architecture/yaml-parser.d.ts +13 -0
- package/dist/architecture/yaml-parser.d.ts.map +1 -0
- package/dist/architecture/yaml-parser.js +234 -0
- package/dist/config/constants.d.ts +9 -0
- package/dist/config/constants.d.ts.map +1 -0
- package/dist/config/constants.js +20 -0
- package/dist/config/index.d.ts +9 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +8 -0
- package/dist/config/loader.d.ts +41 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/loader.js +76 -0
- package/dist/config/nudge-config.d.ts +35 -0
- package/dist/config/nudge-config.d.ts.map +1 -0
- package/dist/config/nudge-config.js +34 -0
- package/dist/config/types.d.ts +30 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +4 -0
- package/dist/contracts/index.d.ts +14 -0
- package/dist/contracts/index.d.ts.map +1 -0
- package/dist/contracts/index.js +13 -0
- package/dist/contracts/schemas/aps.schema.d.ts +269 -0
- package/dist/contracts/schemas/aps.schema.d.ts.map +1 -0
- package/dist/contracts/schemas/aps.schema.js +183 -0
- package/dist/contracts/schemas/index.d.ts +12 -0
- package/dist/contracts/schemas/index.d.ts.map +1 -0
- package/dist/contracts/schemas/index.js +14 -0
- package/dist/contracts/schemas/json-schema.d.ts +14 -0
- package/dist/contracts/schemas/json-schema.d.ts.map +1 -0
- package/dist/contracts/schemas/json-schema.js +31 -0
- package/dist/contracts/schemas/warning.schema.d.ts +171 -0
- package/dist/contracts/schemas/warning.schema.d.ts.map +1 -0
- package/dist/contracts/schemas/warning.schema.js +123 -0
- package/dist/contracts/types/gate.types.d.ts +194 -0
- package/dist/contracts/types/gate.types.d.ts.map +1 -0
- package/dist/contracts/types/gate.types.js +19 -0
- package/dist/contracts/types/index.d.ts +9 -0
- package/dist/contracts/types/index.d.ts.map +1 -0
- package/dist/contracts/types/index.js +8 -0
- package/dist/crypto/hash.d.ts +47 -0
- package/dist/crypto/hash.d.ts.map +1 -0
- package/dist/crypto/hash.js +110 -0
- package/dist/crypto/index.d.ts +7 -0
- package/dist/crypto/index.d.ts.map +1 -0
- package/dist/crypto/index.js +6 -0
- package/dist/drift/index.d.ts +6 -0
- package/dist/drift/index.d.ts.map +1 -0
- package/dist/drift/index.js +5 -0
- package/dist/drift/report-generator.d.ts +21 -0
- package/dist/drift/report-generator.d.ts.map +1 -0
- package/dist/drift/report-generator.js +240 -0
- package/dist/drift/snapshot-capture.d.ts +26 -0
- package/dist/drift/snapshot-capture.d.ts.map +1 -0
- package/dist/drift/snapshot-capture.js +195 -0
- package/dist/drift/snapshot-compare.d.ts +50 -0
- package/dist/drift/snapshot-compare.d.ts.map +1 -0
- package/dist/drift/snapshot-compare.js +142 -0
- package/dist/drift/snapshot-schema.d.ts +197 -0
- package/dist/drift/snapshot-schema.d.ts.map +1 -0
- package/dist/drift/snapshot-schema.js +193 -0
- package/dist/drift/snapshot-storage.d.ts +25 -0
- package/dist/drift/snapshot-storage.d.ts.map +1 -0
- package/dist/drift/snapshot-storage.js +179 -0
- package/dist/explain/antipattern-explainer.d.ts +4 -0
- package/dist/explain/antipattern-explainer.d.ts.map +1 -0
- package/dist/explain/antipattern-explainer.js +196 -0
- package/dist/explain/boundary-explainer.d.ts +5 -0
- package/dist/explain/boundary-explainer.d.ts.map +1 -0
- package/dist/explain/boundary-explainer.js +261 -0
- package/dist/explain/explain-service.d.ts +19 -0
- package/dist/explain/explain-service.d.ts.map +1 -0
- package/dist/explain/explain-service.js +106 -0
- package/dist/explain/index.d.ts +7 -0
- package/dist/explain/index.d.ts.map +1 -0
- package/dist/explain/index.js +5 -0
- package/dist/explain/template-loader.d.ts +9 -0
- package/dist/explain/template-loader.d.ts.map +1 -0
- package/dist/explain/template-loader.js +51 -0
- package/dist/explain/types.d.ts +46 -0
- package/dist/explain/types.d.ts.map +1 -0
- package/dist/explain/types.js +31 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +37 -0
- package/dist/provenance/collector.d.ts +86 -0
- package/dist/provenance/collector.d.ts.map +1 -0
- package/dist/provenance/collector.js +425 -0
- package/dist/provenance/git-ai-standard/git-notes.d.ts +85 -0
- package/dist/provenance/git-ai-standard/git-notes.d.ts.map +1 -0
- package/dist/provenance/git-ai-standard/git-notes.js +292 -0
- package/dist/provenance/git-ai-standard/index.d.ts +44 -0
- package/dist/provenance/git-ai-standard/index.d.ts.map +1 -0
- package/dist/provenance/git-ai-standard/index.js +47 -0
- package/dist/provenance/git-ai-standard/serializer.d.ts +54 -0
- package/dist/provenance/git-ai-standard/serializer.d.ts.map +1 -0
- package/dist/provenance/git-ai-standard/serializer.js +224 -0
- package/dist/provenance/git-ai-standard/session.d.ts +51 -0
- package/dist/provenance/git-ai-standard/session.d.ts.map +1 -0
- package/dist/provenance/git-ai-standard/session.js +118 -0
- package/dist/provenance/git-ai-standard/types.d.ts +173 -0
- package/dist/provenance/git-ai-standard/types.d.ts.map +1 -0
- package/dist/provenance/git-ai-standard/types.js +109 -0
- package/dist/provenance/index.d.ts +5 -0
- package/dist/provenance/index.d.ts.map +1 -0
- package/dist/provenance/index.js +6 -0
- package/dist/provenance/store.d.ts +83 -0
- package/dist/provenance/store.d.ts.map +1 -0
- package/dist/provenance/store.js +248 -0
- package/dist/provenance/types.d.ts +160 -0
- package/dist/provenance/types.d.ts.map +1 -0
- package/dist/provenance/types.js +112 -0
- package/dist/suppression/index.d.ts +4 -0
- package/dist/suppression/index.d.ts.map +1 -0
- package/dist/suppression/index.js +3 -0
- package/dist/suppression/parser.d.ts +31 -0
- package/dist/suppression/parser.d.ts.map +1 -0
- package/dist/suppression/parser.js +219 -0
- package/dist/suppression/service.d.ts +29 -0
- package/dist/suppression/service.d.ts.map +1 -0
- package/dist/suppression/service.js +132 -0
- package/dist/suppression/store.d.ts +61 -0
- package/dist/suppression/store.d.ts.map +1 -0
- package/dist/suppression/store.js +169 -0
- package/dist/utils/debug.d.ts +48 -0
- package/dist/utils/debug.d.ts.map +1 -0
- package/dist/utils/debug.js +100 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +3 -0
- package/dist/utils/path-safety.d.ts +21 -0
- package/dist/utils/path-safety.d.ts.map +1 -0
- package/dist/utils/path-safety.js +49 -0
- package/dist/utils/severity.d.ts +37 -0
- package/dist/utils/severity.d.ts.map +1 -0
- package/dist/utils/severity.js +22 -0
- package/dist/validation/aps-validator.d.ts +66 -0
- package/dist/validation/aps-validator.d.ts.map +1 -0
- package/dist/validation/aps-validator.js +173 -0
- package/dist/validation/errors.d.ts +52 -0
- package/dist/validation/errors.d.ts.map +1 -0
- package/dist/validation/errors.js +115 -0
- package/dist/validation/index.d.ts +8 -0
- package/dist/validation/index.d.ts.map +1 -0
- package/dist/validation/index.js +13 -0
- package/dist/warnings/index.d.ts +2 -0
- package/dist/warnings/index.d.ts.map +1 -0
- package/dist/warnings/index.js +1 -0
- package/dist/warnings/warning-id.d.ts +180 -0
- package/dist/warnings/warning-id.d.ts.map +1 -0
- package/dist/warnings/warning-id.js +257 -0
- package/package.json +79 -0
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Anti-pattern Catalogue
|
|
3
|
+
*
|
|
4
|
+
* Defines the built-in anti-patterns that Anvil detects. Each pattern has:
|
|
5
|
+
* - ID: Unique identifier (AP-001, AP-002, etc.)
|
|
6
|
+
* - Detection: Regex or AST-based detection method
|
|
7
|
+
* - Messaging: Title, explanation, and suggestion for warnings
|
|
8
|
+
* - Configuration: Severity, confidence, allowlist, threshold, etc.
|
|
9
|
+
*
|
|
10
|
+
* @module antipattern/patterns
|
|
11
|
+
*/
|
|
12
|
+
import { HTML_PATTERNS } from './patterns-html.js';
|
|
13
|
+
import { CSS_PATTERNS } from './patterns-css.js';
|
|
14
|
+
// =============================================================================
|
|
15
|
+
// Pattern Definitions
|
|
16
|
+
// =============================================================================
|
|
17
|
+
/**
|
|
18
|
+
* AP-001: Broad eslint-disable (file-level or block without rule)
|
|
19
|
+
*
|
|
20
|
+
* Detects `eslint-disable` comments that disable all rules, which:
|
|
21
|
+
* - Silences all linting errors in scope
|
|
22
|
+
* - Makes code review harder
|
|
23
|
+
* - Often masks multiple issues
|
|
24
|
+
*/
|
|
25
|
+
const AP001_BROAD_ESLINT_DISABLE = {
|
|
26
|
+
id: 'AP-001',
|
|
27
|
+
name: 'Broad eslint-disable',
|
|
28
|
+
category: 'escape-hatch',
|
|
29
|
+
severity: 'warning',
|
|
30
|
+
confidence: 'high',
|
|
31
|
+
detection: {
|
|
32
|
+
type: 'regex',
|
|
33
|
+
// Matches /* eslint-disable */ without specific rules
|
|
34
|
+
// Does NOT match eslint-disable-next-line or eslint-disable-line
|
|
35
|
+
pattern: String.raw `/\*\s*eslint-disable\s*\*/|//\s*eslint-disable(?!-next-line|-line)\s*$`,
|
|
36
|
+
},
|
|
37
|
+
title: 'Broad eslint-disable added',
|
|
38
|
+
explanation: 'Disabling all ESLint rules hides legitimate issues and makes code harder to maintain. ' +
|
|
39
|
+
'This pattern indicates technical debt that should be addressed.',
|
|
40
|
+
suggestion: 'Disable specific rules with /* eslint-disable rule-name */ or fix the underlying issues.',
|
|
41
|
+
nudge: "Don't disable all linting rules. Identify which specific rule is failing and " +
|
|
42
|
+
'either fix the underlying issue or disable only that one rule with ' +
|
|
43
|
+
'`/* eslint-disable specific-rule */`. Blanket disables hide real problems.',
|
|
44
|
+
enabled: true,
|
|
45
|
+
optIn: false,
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* AP-002: Rule-specific eslint-disable
|
|
49
|
+
*
|
|
50
|
+
* Detects `eslint-disable rule-name` comments. These are less problematic than
|
|
51
|
+
* broad disables but still warrant attention during review.
|
|
52
|
+
*/
|
|
53
|
+
const AP002_RULE_SPECIFIC_ESLINT_DISABLE = {
|
|
54
|
+
id: 'AP-002',
|
|
55
|
+
name: 'Rule-specific eslint-disable',
|
|
56
|
+
category: 'escape-hatch',
|
|
57
|
+
severity: 'info',
|
|
58
|
+
confidence: 'high',
|
|
59
|
+
detection: {
|
|
60
|
+
type: 'regex',
|
|
61
|
+
// Matches eslint-disable with specific rule(s)
|
|
62
|
+
// e.g., /* eslint-disable @typescript-eslint/no-explicit-any */
|
|
63
|
+
// or eslint-disable-next-line no-console
|
|
64
|
+
pattern: String.raw `eslint-disable(?:-next-line|-line)?\s+[\w@/-]+`,
|
|
65
|
+
},
|
|
66
|
+
title: 'Rule-specific eslint-disable',
|
|
67
|
+
explanation: 'While better than disabling all rules, targeted disables still indicate code that violates linting standards. ' +
|
|
68
|
+
'Consider if the disable is necessary or if the code can be improved.',
|
|
69
|
+
suggestion: 'Add a comment explaining why this rule needs to be disabled here.',
|
|
70
|
+
nudge: 'Before disabling this rule, try to fix the code so it passes. If the disable ' +
|
|
71
|
+
'is genuinely necessary, add a comment explaining why this specific case ' +
|
|
72
|
+
"can't follow the rule.",
|
|
73
|
+
enabled: true,
|
|
74
|
+
optIn: true, // Noisy - opt-in only
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* AP-003: Explicit `any` type usage
|
|
78
|
+
*
|
|
79
|
+
* Detects explicit use of `any` type in TypeScript. This bypasses type checking
|
|
80
|
+
* and can hide bugs.
|
|
81
|
+
*/
|
|
82
|
+
const AP003_ANY_TYPE = {
|
|
83
|
+
id: 'AP-003',
|
|
84
|
+
name: 'Explicit any type',
|
|
85
|
+
category: 'type-safety',
|
|
86
|
+
severity: 'warning',
|
|
87
|
+
confidence: 'high',
|
|
88
|
+
detection: {
|
|
89
|
+
type: 'regex',
|
|
90
|
+
// Matches `: any`, `as any`, `<any>` type assertions
|
|
91
|
+
// Careful to avoid matching words containing "any" like "company"
|
|
92
|
+
pattern: String.raw `:\s*any\b|as\s+any\b|<any>`,
|
|
93
|
+
},
|
|
94
|
+
title: 'Explicit any type usage',
|
|
95
|
+
explanation: 'Using `any` defeats the purpose of TypeScript by disabling type checking. ' +
|
|
96
|
+
'This can hide bugs and makes refactoring harder.',
|
|
97
|
+
suggestion: 'Use `unknown` for truly unknown types, or define a proper interface/type. ' +
|
|
98
|
+
'For third-party libraries, consider using or creating type definitions.',
|
|
99
|
+
nudge: "Don't use `any` here. Think about what type this value actually holds and " +
|
|
100
|
+
'declare it explicitly. If it comes from an API, define an interface for the ' +
|
|
101
|
+
'response shape. If the type is truly unknown, use `unknown` and narrow it ' +
|
|
102
|
+
'with type guards before use.',
|
|
103
|
+
enabled: true,
|
|
104
|
+
optIn: false,
|
|
105
|
+
allowlist: ['*.d.ts', '**/__mocks__/**', '**/test/**/*.ts', '**/__tests__/**'],
|
|
106
|
+
};
|
|
107
|
+
/**
|
|
108
|
+
* AP-004: @ts-ignore directive
|
|
109
|
+
*
|
|
110
|
+
* Detects `@ts-ignore` which suppresses ALL TypeScript errors on the next line.
|
|
111
|
+
*/
|
|
112
|
+
const AP004_TS_IGNORE = {
|
|
113
|
+
id: 'AP-004',
|
|
114
|
+
name: '@ts-ignore directive',
|
|
115
|
+
category: 'type-safety',
|
|
116
|
+
severity: 'warning',
|
|
117
|
+
confidence: 'high',
|
|
118
|
+
detection: {
|
|
119
|
+
type: 'regex',
|
|
120
|
+
pattern: String.raw `@ts-ignore`,
|
|
121
|
+
},
|
|
122
|
+
title: '@ts-ignore suppresses all errors',
|
|
123
|
+
explanation: '@ts-ignore suppresses ALL TypeScript errors on the next line, including legitimate issues. ' +
|
|
124
|
+
'This can hide bugs introduced by code changes.',
|
|
125
|
+
suggestion: 'Use @ts-expect-error with a description instead, which fails if the expected error disappears. ' +
|
|
126
|
+
'Better yet, fix the underlying type issue.',
|
|
127
|
+
nudge: "Don't suppress this TypeScript error — fix it. If you must suppress, use " +
|
|
128
|
+
'`@ts-expect-error` instead so it fails when the underlying issue is ' +
|
|
129
|
+
'resolved. But first, read the actual error message and address the type ' +
|
|
130
|
+
'mismatch directly.',
|
|
131
|
+
enabled: true,
|
|
132
|
+
optIn: false,
|
|
133
|
+
allowlist: ['**/*.test.ts', '**/*.spec.ts', '**/__tests__/**'],
|
|
134
|
+
};
|
|
135
|
+
/**
|
|
136
|
+
* AP-005: @ts-expect-error directive
|
|
137
|
+
*
|
|
138
|
+
* Detects `@ts-expect-error` which is safer than @ts-ignore but still indicates
|
|
139
|
+
* intentional type issues.
|
|
140
|
+
*/
|
|
141
|
+
const AP005_TS_EXPECT_ERROR = {
|
|
142
|
+
id: 'AP-005',
|
|
143
|
+
name: '@ts-expect-error directive',
|
|
144
|
+
category: 'type-safety',
|
|
145
|
+
severity: 'info',
|
|
146
|
+
confidence: 'high',
|
|
147
|
+
detection: {
|
|
148
|
+
type: 'regex',
|
|
149
|
+
pattern: String.raw `@ts-expect-error`,
|
|
150
|
+
},
|
|
151
|
+
title: '@ts-expect-error used',
|
|
152
|
+
explanation: '@ts-expect-error is safer than @ts-ignore as it fails when the error disappears. ' +
|
|
153
|
+
'However, it still indicates intentional type system workarounds.',
|
|
154
|
+
suggestion: 'Consider if the underlying type issue can be fixed. ' +
|
|
155
|
+
'If not, ensure the @ts-expect-error comment explains why.',
|
|
156
|
+
nudge: 'This type error is being suppressed rather than fixed. Read the error ' +
|
|
157
|
+
'message and resolve the type mismatch. If it is a genuine limitation of the ' +
|
|
158
|
+
'type system, keep the `@ts-expect-error` but ensure the comment explains ' +
|
|
159
|
+
'exactly why.',
|
|
160
|
+
enabled: true,
|
|
161
|
+
optIn: true, // Often legitimate in tests
|
|
162
|
+
allowlist: ['**/*.test.ts', '**/*.spec.ts', '**/__tests__/**'],
|
|
163
|
+
};
|
|
164
|
+
/**
|
|
165
|
+
* AP-006: Empty catch block
|
|
166
|
+
*
|
|
167
|
+
* Detects catch blocks that swallow errors without handling them.
|
|
168
|
+
*/
|
|
169
|
+
const AP006_EMPTY_CATCH = {
|
|
170
|
+
id: 'AP-006',
|
|
171
|
+
name: 'Empty catch block',
|
|
172
|
+
category: 'error-handling',
|
|
173
|
+
severity: 'warning',
|
|
174
|
+
confidence: 'medium',
|
|
175
|
+
detection: {
|
|
176
|
+
type: 'regex',
|
|
177
|
+
// Matches catch blocks with only whitespace/comments inside
|
|
178
|
+
pattern: String.raw `catch\s*\([^)]*\)\s*\{\s*(?://[^\n]*\s*)?\}`,
|
|
179
|
+
},
|
|
180
|
+
title: 'Empty catch block swallows errors',
|
|
181
|
+
explanation: 'Empty catch blocks silently swallow errors, making debugging difficult. ' +
|
|
182
|
+
'Errors should be logged, re-thrown, or explicitly handled.',
|
|
183
|
+
suggestion: 'At minimum, log the error for debugging. Consider if the error should be re-thrown ' +
|
|
184
|
+
'or if specific recovery logic is needed.',
|
|
185
|
+
nudge: "Don't swallow this error silently. At minimum, log it so failures are " +
|
|
186
|
+
'visible. Better: decide whether this error is recoverable (handle it) or ' +
|
|
187
|
+
'not (re-throw it). Silent catch blocks make debugging impossible.',
|
|
188
|
+
enabled: true,
|
|
189
|
+
optIn: false,
|
|
190
|
+
};
|
|
191
|
+
/**
|
|
192
|
+
* AP-007: Console usage in production code
|
|
193
|
+
*
|
|
194
|
+
* Detects console.log, console.warn, etc. in production code.
|
|
195
|
+
*/
|
|
196
|
+
const AP007_CONSOLE_IN_PROD = {
|
|
197
|
+
id: 'AP-007',
|
|
198
|
+
name: 'Console in production code',
|
|
199
|
+
category: 'code-quality',
|
|
200
|
+
severity: 'info',
|
|
201
|
+
confidence: 'medium',
|
|
202
|
+
detection: {
|
|
203
|
+
type: 'regex',
|
|
204
|
+
pattern: String.raw `console\.(log|warn|info|debug)\s*\(`,
|
|
205
|
+
},
|
|
206
|
+
title: 'Console statement in production code',
|
|
207
|
+
explanation: 'Console statements should not appear in production code. They can leak sensitive information, ' +
|
|
208
|
+
'clutter the console, and indicate incomplete debugging.',
|
|
209
|
+
suggestion: 'Use a proper logging library with log levels, or remove the console statement. ' +
|
|
210
|
+
'console.error is acceptable for actual error conditions.',
|
|
211
|
+
nudge: 'Remove this console statement or replace it with a proper logger that ' +
|
|
212
|
+
'supports log levels. Console output in production leaks information and ' +
|
|
213
|
+
'clutters output. If this is intentional debugging, wrap it in a ' +
|
|
214
|
+
'development-only check.',
|
|
215
|
+
enabled: true,
|
|
216
|
+
optIn: true, // Noisy in development
|
|
217
|
+
allowlist: ['**/*.test.ts', '**/*.spec.ts', '**/scripts/**', '**/cli/**'],
|
|
218
|
+
};
|
|
219
|
+
// =============================================================================
|
|
220
|
+
// Pattern Registry
|
|
221
|
+
// =============================================================================
|
|
222
|
+
/**
|
|
223
|
+
* All built-in anti-patterns
|
|
224
|
+
*/
|
|
225
|
+
export const PATTERNS = [
|
|
226
|
+
AP001_BROAD_ESLINT_DISABLE,
|
|
227
|
+
AP002_RULE_SPECIFIC_ESLINT_DISABLE,
|
|
228
|
+
AP003_ANY_TYPE,
|
|
229
|
+
AP004_TS_IGNORE,
|
|
230
|
+
AP005_TS_EXPECT_ERROR,
|
|
231
|
+
AP006_EMPTY_CATCH,
|
|
232
|
+
AP007_CONSOLE_IN_PROD,
|
|
233
|
+
...HTML_PATTERNS,
|
|
234
|
+
...CSS_PATTERNS,
|
|
235
|
+
];
|
|
236
|
+
// =============================================================================
|
|
237
|
+
// Lookup Functions
|
|
238
|
+
// =============================================================================
|
|
239
|
+
/**
|
|
240
|
+
* Get a pattern by ID
|
|
241
|
+
*
|
|
242
|
+
* @param id - Pattern ID (e.g., 'AP-001')
|
|
243
|
+
* @returns The pattern definition, or undefined if not found
|
|
244
|
+
*
|
|
245
|
+
* @example
|
|
246
|
+
* ```ts
|
|
247
|
+
* const pattern = getPattern('AP-001');
|
|
248
|
+
* console.log(pattern?.name); // 'Broad eslint-disable'
|
|
249
|
+
* ```
|
|
250
|
+
*/
|
|
251
|
+
export function getPattern(id) {
|
|
252
|
+
return PATTERNS.find((p) => p.id === id);
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Get all patterns in a category
|
|
256
|
+
*
|
|
257
|
+
* @param category - The category to filter by
|
|
258
|
+
* @returns Array of patterns in that category
|
|
259
|
+
*
|
|
260
|
+
* @example
|
|
261
|
+
* ```ts
|
|
262
|
+
* const escapeHatches = getPatternsByCategory('escape-hatch');
|
|
263
|
+
* console.log(escapeHatches.map(p => p.id)); // ['AP-001', 'AP-002']
|
|
264
|
+
* ```
|
|
265
|
+
*/
|
|
266
|
+
export function getPatternsByCategory(category) {
|
|
267
|
+
return PATTERNS.filter((p) => p.category === category);
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Get all enabled patterns (respects enabled flag, not optIn)
|
|
271
|
+
*
|
|
272
|
+
* @returns Array of enabled patterns
|
|
273
|
+
*/
|
|
274
|
+
export function getEnabledPatterns() {
|
|
275
|
+
return PATTERNS.filter((p) => p.enabled);
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Get all default patterns (enabled and not opt-in)
|
|
279
|
+
*
|
|
280
|
+
* @returns Array of patterns that are enabled by default
|
|
281
|
+
*/
|
|
282
|
+
export function getDefaultPatterns() {
|
|
283
|
+
return PATTERNS.filter((p) => p.enabled && !p.optIn);
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Get pattern IDs for all patterns
|
|
287
|
+
*
|
|
288
|
+
* @returns Array of pattern IDs
|
|
289
|
+
*/
|
|
290
|
+
export function getPatternIds() {
|
|
291
|
+
return PATTERNS.map((p) => p.id);
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Check if a pattern ID is valid
|
|
295
|
+
*
|
|
296
|
+
* @param id - Pattern ID to check
|
|
297
|
+
* @returns true if the ID exists in the catalogue
|
|
298
|
+
*/
|
|
299
|
+
export function isValidPatternId(id) {
|
|
300
|
+
return PATTERNS.some((p) => p.id === id);
|
|
301
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Anti-pattern scanner
|
|
3
|
+
*
|
|
4
|
+
* Scans source files for anti-patterns using regex-based detection.
|
|
5
|
+
*/
|
|
6
|
+
import type { Warning } from './types.js';
|
|
7
|
+
export interface ScanOptions {
|
|
8
|
+
/** Pattern IDs to check (default: all default patterns) */
|
|
9
|
+
patterns?: string[];
|
|
10
|
+
/** Include opt-in patterns (default: false) */
|
|
11
|
+
includeOptIn?: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface ScanResult {
|
|
14
|
+
/** File path that was scanned */
|
|
15
|
+
file: string;
|
|
16
|
+
/** Warnings found in the file */
|
|
17
|
+
warnings: Warning[];
|
|
18
|
+
/** Pattern IDs that were checked */
|
|
19
|
+
patternsChecked: string[];
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Scan a file's content for anti-patterns
|
|
23
|
+
*/
|
|
24
|
+
export declare function scanFile(filePath: string, content: string, options?: ScanOptions): ScanResult;
|
|
25
|
+
/**
|
|
26
|
+
* Scan multiple files for anti-patterns
|
|
27
|
+
*/
|
|
28
|
+
export declare function scanFiles(files: Array<{
|
|
29
|
+
path: string;
|
|
30
|
+
content: string;
|
|
31
|
+
}>, options?: ScanOptions): ScanResult[];
|
|
32
|
+
//# sourceMappingURL=scanner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scanner.d.ts","sourceRoot":"","sources":["../../src/antipattern/scanner.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAe,MAAM,YAAY,CAAC;AAGvD,MAAM,WAAW,WAAW;IAC1B,2DAA2D;IAC3D,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,+CAA+C;IAC/C,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,UAAU;IACzB,iCAAiC;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,iCAAiC;IACjC,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,oCAAoC;IACpC,eAAe,EAAE,MAAM,EAAE,CAAC;CAC3B;AAkDD;;GAEG;AACH,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,UAAU,CAwC7F;AAED;;GAEG;AACH,wBAAgB,SAAS,CACvB,KAAK,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,EAC/C,OAAO,CAAC,EAAE,WAAW,GACpB,UAAU,EAAE,CAEd"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Anti-pattern scanner
|
|
3
|
+
*
|
|
4
|
+
* Scans source files for anti-patterns using regex-based detection.
|
|
5
|
+
*/
|
|
6
|
+
import { minimatch } from 'minimatch';
|
|
7
|
+
import { getDefaultPatterns, getEnabledPatterns, getPattern } from './patterns.js';
|
|
8
|
+
function getPatternsToCheck(options = {}) {
|
|
9
|
+
const { patterns: patternIds, includeOptIn = false } = options;
|
|
10
|
+
if (patternIds && patternIds.length > 0) {
|
|
11
|
+
return patternIds.map((id) => getPattern(id)).filter((p) => p !== undefined);
|
|
12
|
+
}
|
|
13
|
+
return includeOptIn ? getEnabledPatterns() : getDefaultPatterns();
|
|
14
|
+
}
|
|
15
|
+
/** Default file extensions for legacy patterns that predate HTML/CSS support */
|
|
16
|
+
const LEGACY_JS_TS_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'];
|
|
17
|
+
function matchesFileExtension(filePath, fileExtensions) {
|
|
18
|
+
const ext = filePath.substring(filePath.lastIndexOf('.'));
|
|
19
|
+
return fileExtensions.includes(ext.toLowerCase());
|
|
20
|
+
}
|
|
21
|
+
function isFileAllowlisted(filePath, allowlist) {
|
|
22
|
+
if (!allowlist || allowlist.length === 0)
|
|
23
|
+
return false;
|
|
24
|
+
return allowlist.some((pattern) => minimatch(filePath, pattern, { matchBase: true }));
|
|
25
|
+
}
|
|
26
|
+
function createWarningFromMatch(pattern, filePath, line, column) {
|
|
27
|
+
return {
|
|
28
|
+
id: pattern.id,
|
|
29
|
+
category: 'anti-pattern',
|
|
30
|
+
severity: pattern.severity,
|
|
31
|
+
confidence: pattern.confidence,
|
|
32
|
+
title: pattern.title,
|
|
33
|
+
message: `Found ${pattern.name} at line ${line}`,
|
|
34
|
+
explanation: pattern.explanation,
|
|
35
|
+
suggestion: pattern.suggestion,
|
|
36
|
+
...(pattern.nudge ? { nudge: pattern.nudge } : {}),
|
|
37
|
+
location: {
|
|
38
|
+
file: filePath,
|
|
39
|
+
line,
|
|
40
|
+
column,
|
|
41
|
+
},
|
|
42
|
+
pattern: pattern.id,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Scan a file's content for anti-patterns
|
|
47
|
+
*/
|
|
48
|
+
export function scanFile(filePath, content, options) {
|
|
49
|
+
const patterns = getPatternsToCheck(options);
|
|
50
|
+
const warnings = [];
|
|
51
|
+
const lines = content.split('\n');
|
|
52
|
+
for (const pattern of patterns) {
|
|
53
|
+
if (pattern.detection.type !== 'regex')
|
|
54
|
+
continue;
|
|
55
|
+
// File extension matching:
|
|
56
|
+
// - If fileExtensions is set, use it explicitly
|
|
57
|
+
// - If allFileTypes is true, skip extension check (matches everything)
|
|
58
|
+
// - Otherwise, default to JS/TS extensions (legacy behavior prevents false positives on HTML/CSS)
|
|
59
|
+
const effectiveExtensions = pattern.fileExtensions ?? (pattern.allFileTypes ? undefined : LEGACY_JS_TS_EXTENSIONS);
|
|
60
|
+
if (effectiveExtensions && !matchesFileExtension(filePath, effectiveExtensions))
|
|
61
|
+
continue;
|
|
62
|
+
if (isFileAllowlisted(filePath, pattern.allowlist))
|
|
63
|
+
continue;
|
|
64
|
+
const regexPattern = pattern.detection.pattern;
|
|
65
|
+
if (!regexPattern)
|
|
66
|
+
continue;
|
|
67
|
+
const regex = new RegExp(regexPattern, 'g');
|
|
68
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
|
|
69
|
+
const line = lines[lineIndex];
|
|
70
|
+
const lineNumber = lineIndex + 1;
|
|
71
|
+
let match;
|
|
72
|
+
regex.lastIndex = 0;
|
|
73
|
+
while ((match = regex.exec(line)) !== null) {
|
|
74
|
+
warnings.push(createWarningFromMatch(pattern, filePath, lineNumber, match.index));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
file: filePath,
|
|
80
|
+
warnings,
|
|
81
|
+
patternsChecked: patterns.map((p) => p.id),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Scan multiple files for anti-patterns
|
|
86
|
+
*/
|
|
87
|
+
export function scanFiles(files, options) {
|
|
88
|
+
return files.map((file) => scanFile(file.path, file.content, options));
|
|
89
|
+
}
|