react-code-smell-detector 1.2.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +46 -0
  3. package/dist/analyzer.d.ts.map +1 -1
  4. package/dist/analyzer.js +18 -1
  5. package/dist/cli.js +93 -26
  6. package/dist/detectors/complexity.d.ts +17 -0
  7. package/dist/detectors/complexity.d.ts.map +1 -0
  8. package/dist/detectors/complexity.js +69 -0
  9. package/dist/detectors/imports.d.ts +22 -0
  10. package/dist/detectors/imports.d.ts.map +1 -0
  11. package/dist/detectors/imports.js +210 -0
  12. package/dist/detectors/index.d.ts +3 -0
  13. package/dist/detectors/index.d.ts.map +1 -1
  14. package/dist/detectors/index.js +4 -0
  15. package/dist/detectors/memoryLeak.d.ts +7 -0
  16. package/dist/detectors/memoryLeak.d.ts.map +1 -0
  17. package/dist/detectors/memoryLeak.js +111 -0
  18. package/dist/fixer.d.ts +23 -0
  19. package/dist/fixer.d.ts.map +1 -0
  20. package/dist/fixer.js +133 -0
  21. package/dist/git.d.ts +28 -0
  22. package/dist/git.d.ts.map +1 -0
  23. package/dist/git.js +117 -0
  24. package/dist/reporter.js +13 -0
  25. package/dist/types/index.d.ts +7 -1
  26. package/dist/types/index.d.ts.map +1 -1
  27. package/dist/types/index.js +9 -0
  28. package/dist/watcher.d.ts +16 -0
  29. package/dist/watcher.d.ts.map +1 -0
  30. package/dist/watcher.js +89 -0
  31. package/package.json +8 -2
  32. package/src/analyzer.ts +0 -324
  33. package/src/cli.ts +0 -159
  34. package/src/detectors/accessibility.ts +0 -212
  35. package/src/detectors/deadCode.ts +0 -163
  36. package/src/detectors/debug.ts +0 -103
  37. package/src/detectors/dependencyArray.ts +0 -176
  38. package/src/detectors/hooksRules.ts +0 -101
  39. package/src/detectors/index.ts +0 -20
  40. package/src/detectors/javascript.ts +0 -169
  41. package/src/detectors/largeComponent.ts +0 -63
  42. package/src/detectors/magicValues.ts +0 -114
  43. package/src/detectors/memoization.ts +0 -177
  44. package/src/detectors/missingKey.ts +0 -105
  45. package/src/detectors/nestedTernary.ts +0 -75
  46. package/src/detectors/nextjs.ts +0 -124
  47. package/src/detectors/nodejs.ts +0 -199
  48. package/src/detectors/propDrilling.ts +0 -103
  49. package/src/detectors/reactNative.ts +0 -154
  50. package/src/detectors/security.ts +0 -179
  51. package/src/detectors/typescript.ts +0 -151
  52. package/src/detectors/useEffect.ts +0 -117
  53. package/src/htmlReporter.ts +0 -464
  54. package/src/index.ts +0 -4
  55. package/src/parser/index.ts +0 -195
  56. package/src/reporter.ts +0 -291
  57. package/src/types/index.ts +0 -165
  58. package/tsconfig.json +0 -19
@@ -1,163 +0,0 @@
1
- import * as t from '@babel/types';
2
- import { ParsedComponent, getCodeSnippet } from '../parser/index.js';
3
- import { CodeSmell, DetectorConfig, DEFAULT_CONFIG } from '../types/index.js';
4
-
5
- /**
6
- * Detects potentially dead code: unused variables, imports, and functions
7
- */
8
- export function detectDeadCode(
9
- component: ParsedComponent,
10
- filePath: string,
11
- sourceCode: string,
12
- config: DetectorConfig = DEFAULT_CONFIG
13
- ): CodeSmell[] {
14
- if (!config.checkDeadCode) return [];
15
-
16
- const smells: CodeSmell[] = [];
17
-
18
- // Track declared and used identifiers within the component
19
- const declared = new Map<string, { line: number; column: number; type: string }>();
20
- const used = new Set<string>();
21
-
22
- // Collect all declared variables in the component
23
- component.path.traverse({
24
- VariableDeclarator(path) {
25
- if (t.isIdentifier(path.node.id)) {
26
- const loc = path.node.loc;
27
- declared.set(path.node.id.name, {
28
- line: loc?.start.line || 0,
29
- column: loc?.start.column || 0,
30
- type: 'variable',
31
- });
32
- } else if (t.isObjectPattern(path.node.id)) {
33
- // Destructured variables
34
- path.node.id.properties.forEach(prop => {
35
- if (t.isObjectProperty(prop) && t.isIdentifier(prop.value)) {
36
- const loc = prop.loc;
37
- declared.set(prop.value.name, {
38
- line: loc?.start.line || 0,
39
- column: loc?.start.column || 0,
40
- type: 'variable',
41
- });
42
- } else if (t.isRestElement(prop) && t.isIdentifier(prop.argument)) {
43
- const loc = prop.loc;
44
- declared.set(prop.argument.name, {
45
- line: loc?.start.line || 0,
46
- column: loc?.start.column || 0,
47
- type: 'variable',
48
- });
49
- }
50
- });
51
- } else if (t.isArrayPattern(path.node.id)) {
52
- // Array destructured variables
53
- path.node.id.elements.forEach(elem => {
54
- if (t.isIdentifier(elem)) {
55
- const loc = elem.loc;
56
- declared.set(elem.name, {
57
- line: loc?.start.line || 0,
58
- column: loc?.start.column || 0,
59
- type: 'variable',
60
- });
61
- }
62
- });
63
- }
64
- },
65
-
66
- FunctionDeclaration(path) {
67
- if (path.node.id) {
68
- const loc = path.node.loc;
69
- declared.set(path.node.id.name, {
70
- line: loc?.start.line || 0,
71
- column: loc?.start.column || 0,
72
- type: 'function',
73
- });
74
- }
75
- },
76
- });
77
-
78
- // Collect all used identifiers
79
- component.path.traverse({
80
- Identifier(path) {
81
- // Skip if it's a declaration
82
- if (
83
- t.isVariableDeclarator(path.parent) && path.parent.id === path.node ||
84
- t.isFunctionDeclaration(path.parent) && path.parent.id === path.node ||
85
- t.isObjectProperty(path.parent) && path.parent.key === path.node ||
86
- t.isImportSpecifier(path.parent) ||
87
- t.isImportDefaultSpecifier(path.parent)
88
- ) {
89
- return;
90
- }
91
-
92
- // Skip property access (x.y - y is not a reference)
93
- if (t.isMemberExpression(path.parent) && path.parent.property === path.node && !path.parent.computed) {
94
- return;
95
- }
96
-
97
- used.add(path.node.name);
98
- },
99
-
100
- JSXIdentifier(path) {
101
- // Components used in JSX
102
- if (t.isJSXOpeningElement(path.parent) || t.isJSXClosingElement(path.parent)) {
103
- used.add(path.node.name);
104
- }
105
- },
106
- });
107
-
108
- // Find unused declarations
109
- declared.forEach((info, name) => {
110
- // Skip hooks and common patterns
111
- if (name.startsWith('_') || name.startsWith('set')) return;
112
-
113
- // Skip if it's the component name itself
114
- if (name === component.name) return;
115
-
116
- if (!used.has(name)) {
117
- smells.push({
118
- type: 'dead-code',
119
- severity: 'info',
120
- message: `Unused ${info.type} "${name}" in "${component.name}"`,
121
- file: filePath,
122
- line: info.line,
123
- column: info.column,
124
- suggestion: `Remove the unused ${info.type} or use it in the component.`,
125
- codeSnippet: getCodeSnippet(sourceCode, info.line),
126
- });
127
- }
128
- });
129
-
130
- return smells;
131
- }
132
-
133
- /**
134
- * Detects unused imports at the file level
135
- */
136
- export function detectUnusedImports(
137
- imports: Map<string, { source: string; line: number }>,
138
- usedInFile: Set<string>,
139
- filePath: string,
140
- sourceCode: string
141
- ): CodeSmell[] {
142
- const smells: CodeSmell[] = [];
143
-
144
- imports.forEach((info, name) => {
145
- // Skip React imports as they might be used implicitly
146
- if (name === 'React' || info.source === 'react') return;
147
-
148
- if (!usedInFile.has(name)) {
149
- smells.push({
150
- type: 'dead-code',
151
- severity: 'info',
152
- message: `Unused import "${name}" from "${info.source}"`,
153
- file: filePath,
154
- line: info.line,
155
- column: 0,
156
- suggestion: `Remove the unused import: import { ${name} } from '${info.source}'`,
157
- codeSnippet: getCodeSnippet(sourceCode, info.line),
158
- });
159
- }
160
- });
161
-
162
- return smells;
163
- }
@@ -1,103 +0,0 @@
1
- import * as t from '@babel/types';
2
- import { ParsedComponent, getCodeSnippet } from '../parser/index.js';
3
- import { CodeSmell, DetectorConfig, DEFAULT_CONFIG } from '../types/index.js';
4
-
5
- /**
6
- * Detects debug statements that should be removed:
7
- * - console.log/warn/error/debug
8
- * - debugger statements
9
- * - TODO/FIXME/HACK comments (detected via source code)
10
- */
11
- export function detectDebugStatements(
12
- component: ParsedComponent,
13
- filePath: string,
14
- sourceCode: string,
15
- config: DetectorConfig = DEFAULT_CONFIG
16
- ): CodeSmell[] {
17
- if (!config.checkDebugStatements) return [];
18
-
19
- const smells: CodeSmell[] = [];
20
- const reportedLines = new Set<number>();
21
-
22
- // Detect console.* calls
23
- component.path.traverse({
24
- CallExpression(path) {
25
- const { callee } = path.node;
26
-
27
- if (t.isMemberExpression(callee) &&
28
- t.isIdentifier(callee.object) &&
29
- callee.object.name === 'console') {
30
-
31
- const method = t.isIdentifier(callee.property) ? callee.property.name : '';
32
- const debugMethods = ['log', 'warn', 'error', 'debug', 'info', 'trace', 'dir', 'table'];
33
-
34
- if (debugMethods.includes(method)) {
35
- const loc = path.node.loc;
36
- const line = loc?.start.line || 0;
37
-
38
- if (!reportedLines.has(line)) {
39
- reportedLines.add(line);
40
- smells.push({
41
- type: 'debug-statement',
42
- severity: 'warning',
43
- message: `console.${method}() should be removed before production`,
44
- file: filePath,
45
- line,
46
- column: loc?.start.column || 0,
47
- suggestion: 'Remove console statement or use a logging library with environment-based filtering',
48
- codeSnippet: getCodeSnippet(sourceCode, line),
49
- });
50
- }
51
- }
52
- }
53
- },
54
-
55
- // Detect debugger statements
56
- DebuggerStatement(path) {
57
- const loc = path.node.loc;
58
- smells.push({
59
- type: 'debug-statement',
60
- severity: 'error',
61
- message: 'debugger statement must be removed before production',
62
- file: filePath,
63
- line: loc?.start.line || 0,
64
- column: loc?.start.column || 0,
65
- suggestion: 'Remove the debugger statement',
66
- codeSnippet: getCodeSnippet(sourceCode, loc?.start.line || 0),
67
- });
68
- },
69
- });
70
-
71
- // Detect TODO/FIXME/HACK comments in source
72
- const todoPatterns = [
73
- { pattern: /\/\/\s*(TODO|FIXME|HACK|XXX|BUG)[:.\s]/gi, type: 'todo-comment' as const },
74
- { pattern: /\/\*\s*(TODO|FIXME|HACK|XXX|BUG)[:.\s]/gi, type: 'todo-comment' as const },
75
- ];
76
-
77
- const lines = sourceCode.split('\n');
78
- lines.forEach((line, index) => {
79
- const lineNum = index + 1;
80
-
81
- // Only check within component bounds
82
- if (lineNum < component.startLine || lineNum > component.endLine) return;
83
-
84
- todoPatterns.forEach(({ pattern }) => {
85
- const match = line.match(pattern);
86
- if (match) {
87
- const tag = match[0].replace(/[/\*\s:]/g, '').toUpperCase();
88
- smells.push({
89
- type: 'todo-comment',
90
- severity: 'info',
91
- message: `${tag} comment found in "${component.name}"`,
92
- file: filePath,
93
- line: lineNum,
94
- column: 0,
95
- suggestion: `Address the ${tag} or create a ticket to track it`,
96
- codeSnippet: getCodeSnippet(sourceCode, lineNum),
97
- });
98
- }
99
- });
100
- });
101
-
102
- return smells;
103
- }
@@ -1,176 +0,0 @@
1
- import * as t from '@babel/types';
2
- import { ParsedComponent, getCodeSnippet } from '../parser/index.js';
3
- import { CodeSmell, DetectorConfig, DEFAULT_CONFIG } from '../types/index.js';
4
-
5
- /**
6
- * Detects potential issues with useEffect dependency arrays
7
- */
8
- export function detectDependencyArrayIssues(
9
- component: ParsedComponent,
10
- filePath: string,
11
- sourceCode: string,
12
- config: DetectorConfig = DEFAULT_CONFIG
13
- ): CodeSmell[] {
14
- if (!config.checkDependencyArrays) return [];
15
-
16
- const smells: CodeSmell[] = [];
17
-
18
- // Analyze each useEffect
19
- component.hooks.useEffect.forEach(effectCall => {
20
- const args = effectCall.arguments;
21
- const callback = args[0];
22
- const depsArray = args[1];
23
-
24
- const loc = effectCall.loc;
25
- const line = loc?.start.line || 0;
26
-
27
- // Check 1: Missing dependency array entirely
28
- if (!depsArray) {
29
- smells.push({
30
- type: 'dependency-array-issue',
31
- severity: 'warning',
32
- message: `useEffect without dependency array in "${component.name}" runs on every render`,
33
- file: filePath,
34
- line,
35
- column: loc?.start.column || 0,
36
- suggestion: 'Add a dependency array. Use [] for mount-only effect, or [dep1, dep2] for reactive dependencies.',
37
- codeSnippet: getCodeSnippet(sourceCode, line),
38
- });
39
- return;
40
- }
41
-
42
- // Check 2: Empty dependency array with variables used inside
43
- if (t.isArrayExpression(depsArray) && depsArray.elements.length === 0) {
44
- // Collect variables used in the effect callback
45
- const usedVariables = new Set<string>();
46
-
47
- if (t.isArrowFunctionExpression(callback) || t.isFunctionExpression(callback)) {
48
- collectVariablesUsed(callback.body, usedVariables);
49
-
50
- // Filter out common globals and React hooks
51
- const globals = new Set(['console', 'window', 'document', 'setTimeout', 'setInterval',
52
- 'clearTimeout', 'clearInterval', 'fetch', 'Promise', 'JSON', 'Math', 'Date', 'Array',
53
- 'Object', 'String', 'Number', 'Boolean', 'Error', 'undefined', 'null', 'true', 'false']);
54
-
55
- // Check if there are local variables that might need to be dependencies
56
- const potentialMissing = [...usedVariables].filter(v =>
57
- !globals.has(v) &&
58
- !v.startsWith('set') && // Setters from useState are stable
59
- v !== 'props' &&
60
- v !== 'children'
61
- );
62
-
63
- // Only warn if there appear to be external variables used
64
- if (potentialMissing.length > 3) {
65
- smells.push({
66
- type: 'dependency-array-issue',
67
- severity: 'info',
68
- message: `useEffect in "${component.name}" has empty deps array but uses external variables`,
69
- file: filePath,
70
- line,
71
- column: loc?.start.column || 0,
72
- suggestion: `Consider adding dependencies: [${potentialMissing.slice(0, 3).join(', ')}...]`,
73
- codeSnippet: getCodeSnippet(sourceCode, line),
74
- });
75
- }
76
- }
77
- }
78
-
79
- // Check 3: Dependency array with object/array literals
80
- if (t.isArrayExpression(depsArray)) {
81
- depsArray.elements.forEach((elem, index) => {
82
- if (t.isObjectExpression(elem) || t.isArrayExpression(elem)) {
83
- smells.push({
84
- type: 'dependency-array-issue',
85
- severity: 'warning',
86
- message: `Object/array literal in useEffect dependency array in "${component.name}" causes infinite re-renders`,
87
- file: filePath,
88
- line,
89
- column: loc?.start.column || 0,
90
- suggestion: 'Move the object/array to a useMemo or extract to a constant outside the component.',
91
- codeSnippet: getCodeSnippet(sourceCode, line),
92
- });
93
- }
94
-
95
- // Check for inline functions
96
- if (t.isArrowFunctionExpression(elem) || t.isFunctionExpression(elem)) {
97
- smells.push({
98
- type: 'dependency-array-issue',
99
- severity: 'warning',
100
- message: `Inline function in useEffect dependency array in "${component.name}" causes infinite re-renders`,
101
- file: filePath,
102
- line,
103
- column: loc?.start.column || 0,
104
- suggestion: 'Wrap the function in useCallback before using as a dependency.',
105
- codeSnippet: getCodeSnippet(sourceCode, line),
106
- });
107
- }
108
- });
109
- }
110
- });
111
-
112
- // Also check useMemo and useCallback
113
- [...component.hooks.useMemo, ...component.hooks.useCallback].forEach(hookCall => {
114
- const depsArray = hookCall.arguments[1];
115
- const loc = hookCall.loc;
116
- const line = loc?.start.line || 0;
117
-
118
- if (!depsArray) {
119
- const hookName = t.isIdentifier((hookCall.callee as t.Identifier))
120
- ? ((hookCall.callee as t.Identifier).name)
121
- : 'useMemo/useCallback';
122
-
123
- smells.push({
124
- type: 'dependency-array-issue',
125
- severity: 'warning',
126
- message: `${hookName} without dependency array in "${component.name}"`,
127
- file: filePath,
128
- line,
129
- column: loc?.start.column || 0,
130
- suggestion: 'Add a dependency array to specify when the value should be recalculated.',
131
- codeSnippet: getCodeSnippet(sourceCode, line),
132
- });
133
- }
134
- });
135
-
136
- return smells;
137
- }
138
-
139
- /**
140
- * Helper to collect variable names used in a code block
141
- */
142
- function collectVariablesUsed(node: t.Node, variables: Set<string>): void {
143
- if (t.isIdentifier(node)) {
144
- variables.add(node.name);
145
- } else if (t.isMemberExpression(node)) {
146
- if (t.isIdentifier(node.object)) {
147
- variables.add(node.object.name);
148
- } else {
149
- collectVariablesUsed(node.object, variables);
150
- }
151
- } else if (t.isBlockStatement(node)) {
152
- node.body.forEach(stmt => collectVariablesUsed(stmt, variables));
153
- } else if (t.isExpressionStatement(node)) {
154
- collectVariablesUsed(node.expression, variables);
155
- } else if (t.isCallExpression(node)) {
156
- if (t.isIdentifier(node.callee)) {
157
- variables.add(node.callee.name);
158
- }
159
- node.arguments.forEach(arg => {
160
- if (t.isNode(arg)) collectVariablesUsed(arg, variables);
161
- });
162
- } else if (t.isConditionalExpression(node)) {
163
- collectVariablesUsed(node.test, variables);
164
- collectVariablesUsed(node.consequent, variables);
165
- collectVariablesUsed(node.alternate, variables);
166
- } else if (t.isBinaryExpression(node) || t.isLogicalExpression(node)) {
167
- collectVariablesUsed(node.left, variables);
168
- collectVariablesUsed(node.right, variables);
169
- } else if (t.isReturnStatement(node) && node.argument) {
170
- collectVariablesUsed(node.argument, variables);
171
- } else if (t.isIfStatement(node)) {
172
- collectVariablesUsed(node.test, variables);
173
- collectVariablesUsed(node.consequent, variables);
174
- if (node.alternate) collectVariablesUsed(node.alternate, variables);
175
- }
176
- }
@@ -1,101 +0,0 @@
1
- import * as t from '@babel/types';
2
- import { NodePath } from '@babel/traverse';
3
- import { ParsedComponent, getCodeSnippet } from '../parser/index.js';
4
- import { CodeSmell, DetectorConfig, DEFAULT_CONFIG } from '../types/index.js';
5
-
6
- /**
7
- * Detects hooks used inside conditionals or loops (violates Rules of Hooks)
8
- */
9
- export function detectHooksRulesViolations(
10
- component: ParsedComponent,
11
- filePath: string,
12
- sourceCode: string,
13
- config: DetectorConfig = DEFAULT_CONFIG
14
- ): CodeSmell[] {
15
- if (!config.checkHooksRules) return [];
16
-
17
- const smells: CodeSmell[] = [];
18
- const hooks = [
19
- 'useState', 'useEffect', 'useContext', 'useReducer', 'useCallback',
20
- 'useMemo', 'useRef', 'useImperativeHandle', 'useLayoutEffect',
21
- 'useDebugValue', 'useDeferredValue', 'useTransition', 'useId',
22
- ];
23
-
24
- component.path.traverse({
25
- CallExpression(path) {
26
- // Check if it's a hook call
27
- const callee = path.node.callee;
28
- let hookName: string | null = null;
29
-
30
- if (t.isIdentifier(callee) && hooks.includes(callee.name)) {
31
- hookName = callee.name;
32
- } else if (t.isIdentifier(callee) && callee.name.startsWith('use') && callee.name[3]?.toUpperCase() === callee.name[3]) {
33
- // Custom hooks (useXxx pattern)
34
- hookName = callee.name;
35
- }
36
-
37
- if (!hookName) return;
38
-
39
- // Check if hook is inside a conditional or loop
40
- let currentPath: NodePath | null = path.parentPath;
41
-
42
- while (currentPath) {
43
- const node = currentPath.node;
44
-
45
- // Check for conditionals
46
- if (t.isIfStatement(node) || t.isConditionalExpression(node)) {
47
- const loc = path.node.loc;
48
- smells.push({
49
- type: 'hooks-rules-violation',
50
- severity: 'error',
51
- message: `Hook "${hookName}" called inside a conditional in "${component.name}"`,
52
- file: filePath,
53
- line: loc?.start.line || 0,
54
- column: loc?.start.column || 0,
55
- suggestion: 'Hooks must be called at the top level of the component. Move the hook call outside the conditional.',
56
- codeSnippet: getCodeSnippet(sourceCode, loc?.start.line || 0),
57
- });
58
- break;
59
- }
60
-
61
- // Check for loops
62
- if (
63
- t.isForStatement(node) ||
64
- t.isWhileStatement(node) ||
65
- t.isDoWhileStatement(node) ||
66
- t.isForInStatement(node) ||
67
- t.isForOfStatement(node)
68
- ) {
69
- const loc = path.node.loc;
70
- smells.push({
71
- type: 'hooks-rules-violation',
72
- severity: 'error',
73
- message: `Hook "${hookName}" called inside a loop in "${component.name}"`,
74
- file: filePath,
75
- line: loc?.start.line || 0,
76
- column: loc?.start.column || 0,
77
- suggestion: 'Hooks must be called at the top level. Consider restructuring to avoid calling hooks in loops.',
78
- codeSnippet: getCodeSnippet(sourceCode, loc?.start.line || 0),
79
- });
80
- break;
81
- }
82
-
83
- // Check for early return before hook (within same function)
84
- // This would require more complex flow analysis
85
-
86
- // Stop traversing when we reach the component function
87
- if (
88
- t.isFunctionDeclaration(node) ||
89
- t.isArrowFunctionExpression(node) ||
90
- t.isFunctionExpression(node)
91
- ) {
92
- break;
93
- }
94
-
95
- currentPath = currentPath.parentPath;
96
- }
97
- },
98
- });
99
-
100
- return smells;
101
- }
@@ -1,20 +0,0 @@
1
- export { detectUseEffectOveruse } from './useEffect.js';
2
- export { detectPropDrilling, analyzePropDrillingDepth } from './propDrilling.js';
3
- export { detectLargeComponent } from './largeComponent.js';
4
- export { detectUnmemoizedCalculations } from './memoization.js';
5
- export { detectMissingKeys } from './missingKey.js';
6
- export { detectHooksRulesViolations } from './hooksRules.js';
7
- export { detectDependencyArrayIssues } from './dependencyArray.js';
8
- export { detectNestedTernaries } from './nestedTernary.js';
9
- export { detectDeadCode, detectUnusedImports } from './deadCode.js';
10
- export { detectMagicValues } from './magicValues.js';
11
- // Framework-specific detectors
12
- export { detectNextjsIssues } from './nextjs.js';
13
- export { detectReactNativeIssues } from './reactNative.js';
14
- export { detectNodejsIssues } from './nodejs.js';
15
- export { detectJavascriptIssues } from './javascript.js';
16
- export { detectTypescriptIssues } from './typescript.js';
17
- // Debug, Security, Accessibility
18
- export { detectDebugStatements } from './debug.js';
19
- export { detectSecurityIssues } from './security.js';
20
- export { detectAccessibilityIssues } from './accessibility.js';