@sun-asterisk/sunlint 1.3.9 → 1.3.10

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.
@@ -375,6 +375,24 @@
375
375
  }
376
376
  }
377
377
  },
378
+ "C060": {
379
+ "name": "Do not override superclass methods and ignore critical logic",
380
+ "description": "Preserve important behavior or lifecycle logic defined in the superclass to ensure correctness and prevent silent errors.",
381
+ "category": "logging",
382
+ "severity": "warning",
383
+ "languages": ["typescript", "javascript", "dart"],
384
+ "analyzer": "./rules/common/C060_no_override_superclass/analyzer.js",
385
+ "version": "1.0.0",
386
+ "status": "stable",
387
+ "tags": ["logging", "production", "debugging", "console"],
388
+ "strategy": {
389
+ "preferred": "regex",
390
+ "fallbacks": ["regex"],
391
+ "accuracy": {
392
+ "regex": 90
393
+ }
394
+ }
395
+ },
378
396
  "S001": {
379
397
  "name": "Fail Securely",
380
398
  "description": "Verify that if there is an error in access control, the system fails securely",
@@ -71,7 +71,7 @@ function createCliProgram() {
71
71
  .option('--debug', 'Enable debug mode')
72
72
  .option('--ai', 'Enable AI-powered analysis')
73
73
  .option('--no-ai', 'Force disable AI analysis')
74
- .option('--max-semantic-files <number>', 'Symbol table file limit for TypeScript analysis (default: auto)', '0')
74
+ .option('--max-semantic-files <number>', 'Symbol table file limit for TypeScript analysis (default: 1000, -1 for unlimited)')
75
75
  .option('--list-engines', 'List available analysis engines');
76
76
 
77
77
  // ESLint Integration options
@@ -102,7 +102,7 @@ class SemanticEngine {
102
102
  );
103
103
 
104
104
  if (targetFiles) {
105
- console.log(`🎯 Targeted files received: ${targetFiles.length} total, ${semanticFiles.length} TS/JS files`);
105
+ console.log(`🎯 Targeted files received: ${targetFiles.length} total, ${semanticFiles.length} TypeScript/JavaScript files (TS/TSX/JS/JSX)`);
106
106
  if (semanticFiles.length < 10) {
107
107
  console.log(` Files: ${semanticFiles.map(f => path.basename(f)).join(', ')}`);
108
108
  }
@@ -119,7 +119,9 @@ class SemanticEngine {
119
119
  } else if (userMaxFiles === 0) {
120
120
  // Disable semantic analysis
121
121
  maxFiles = 0;
122
- console.log(`🔧 Semantic Engine config: DISABLED semantic analysis (heuristic only)`);
122
+ console.log(`🔧 Semantic Engine config: DISABLED semantic analysis (heuristic only mode)`);
123
+ console.log(` 💡 Semantic analysis explicitly disabled with --max-semantic-files=0`);
124
+ console.log(` 💡 To enable: omit the option (default: 1000) or use --max-semantic-files=1000 (or higher)`);
123
125
  } else if (userMaxFiles > 0) {
124
126
  // User-specified limit
125
127
  maxFiles = Math.min(userMaxFiles, semanticFiles.length);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sun-asterisk/sunlint",
3
- "version": "1.3.9",
3
+ "version": "1.3.10",
4
4
  "description": "☀️ SunLint - Multi-language static analysis tool for code quality and security | Sun* Engineering Standards",
5
5
  "main": "cli.js",
6
6
  "bin": {
@@ -48,6 +48,14 @@ class C024SymbolBasedAnalyzer {
48
48
  }
49
49
 
50
50
  try {
51
+ // skip ignored files
52
+ if (this.isIgnoredFile(filePath)) {
53
+ if (verbose) {
54
+ console.log(`🔍 [C024 Symbol-Based] Skipping ignored file: ${filePath}`);
55
+ }
56
+ return violations;
57
+ }
58
+
51
59
  const sourceFile = this.semanticEngine.project.getSourceFile(filePath);
52
60
  if (!sourceFile) {
53
61
  return violations;
@@ -99,9 +107,7 @@ class C024SymbolBasedAnalyzer {
99
107
  const kind = node.getKind();
100
108
  if (
101
109
  kind === SyntaxKind.StringLiteral ||
102
- kind === SyntaxKind.NumericLiteral ||
103
- kind === SyntaxKind.TrueKeyword ||
104
- kind === SyntaxKind.FalseKeyword
110
+ kind === SyntaxKind.NumericLiteral
105
111
  ) {
106
112
  const text = node.getText().replace(/['"`]/g, ""); // strip quotes
107
113
  if (this.isAllowedLiteral(node, text)) return;
@@ -120,9 +126,27 @@ class C024SymbolBasedAnalyzer {
120
126
  const kind = node.getKind();
121
127
  if (kind === SyntaxKind.VariableDeclaration) {
122
128
  const parentKind = node.getParent()?.getKind();
129
+ // Skip detection for `for ... of` loop variable
130
+ const loopAncestor = node.getFirstAncestor((ancestor) => {
131
+ const kind = ancestor.getKind?.();
132
+ return (
133
+ kind === SyntaxKind.ForOfStatement ||
134
+ kind === SyntaxKind.ForInStatement ||
135
+ kind === SyntaxKind.ForStatement ||
136
+ kind === SyntaxKind.WhileStatement ||
137
+ kind === SyntaxKind.DoStatement ||
138
+ kind === SyntaxKind.SwitchStatement
139
+ );
140
+ });
141
+
142
+ if (loopAncestor) {
143
+ return; // skip for all loop/switch contexts, no matter how nested
144
+ }
145
+
123
146
  if (
124
147
  parentKind === SyntaxKind.VariableDeclarationList &&
125
- node.getParent().getDeclarationKind() === "const"
148
+ node.getParent().getDeclarationKind() === "const" &&
149
+ !node.getInitializer()
126
150
  ) {
127
151
  this.pushViolation(
128
152
  violations,
@@ -154,27 +178,109 @@ class C024SymbolBasedAnalyzer {
154
178
 
155
179
  // --- helper: allow safe literals ---
156
180
  isAllowedLiteral(node, text) {
157
- // skip imports
158
- if (node.getParent()?.getKind() === SyntaxKind.ImportDeclaration) {
181
+ const parent = node.getParent();
182
+
183
+ // 1 Skip imports/exports
184
+ if (parent?.getKind() === SyntaxKind.ImportDeclaration) return true;
185
+ if (parent?.getKind() === SyntaxKind.ExportDeclaration) return true;
186
+
187
+ // 2 Skip literals that are inside call expressions (direct or nested)
188
+ if (
189
+ parent?.getKind() === SyntaxKind.CallExpression ||
190
+ parent?.getFirstAncestorByKind(SyntaxKind.CallExpression)
191
+ ) {
159
192
  return true;
160
193
  }
161
194
 
162
- // allow short strings
195
+ if (
196
+ parent?.getKind() === SyntaxKind.ElementAccessExpression &&
197
+ parent.getArgumentExpression?.() === node
198
+ ) {
199
+ return true; // skip array/object key
200
+ }
201
+
202
+ // 3 Allow short strings
163
203
  if (typeof text === "string" && text.length <= 1) return true;
164
204
 
165
- // allow sentinel numbers
205
+ // 4 Allow sentinel numbers
166
206
  if (text === "0" || text === "1" || text === "-1") return true;
167
207
 
168
- // allow known safe strings (like "UNKNOWN")
208
+ // 5 Allow known safe strings (like "UNKNOWN")
169
209
  if (this.safeStrings.includes(text)) return true;
170
210
 
211
+ // 6 Allow SQL-style placeholders (:variable) inside string/template
212
+ if (typeof text === "string" && /:\w+/.test(text)) {
213
+ return true;
214
+ }
215
+
171
216
  return false;
172
217
  }
173
218
 
174
219
  // helper to check if file is a constants file
175
220
  isConstantsFile(filePath) {
176
221
  const lower = filePath.toLowerCase();
177
- return lower.endsWith("constants.ts") || lower.includes("/constants/");
222
+
223
+ // common suffixes/patterns for utility or structural files
224
+ const ignoredSuffixes = [
225
+ ".constants.ts",
226
+ ".const.ts",
227
+ ".enum.ts",
228
+ ".interface.ts",
229
+ ".response.ts",
230
+ ".request.ts",
231
+ ".res.ts",
232
+ ".req.ts",
233
+ ];
234
+
235
+ // 1 direct suffix match
236
+ if (ignoredSuffixes.some(suffix => lower.endsWith(suffix))) {
237
+ return true;
238
+ }
239
+
240
+ // 2 matches dto.xxx.ts (multi-dot dto files)
241
+ if (/\.dto\.[^.]+\.ts$/.test(lower)) {
242
+ return true;
243
+ }
244
+
245
+ // 3 matches folder-based conventions
246
+ if (
247
+ lower.includes("/constants/") ||
248
+ lower.includes("/enums/") ||
249
+ lower.includes("/interfaces/")
250
+ ) {
251
+ return true;
252
+ }
253
+
254
+ return false;
255
+ }
256
+
257
+
258
+ isIgnoredFile(filePath) {
259
+ const ignoredPatterns = [
260
+ /\.test\./i,
261
+ /\.tests\./i,
262
+ /\.spec\./i,
263
+ /\.mock\./i,
264
+ /\.css$/i,
265
+ /\.scss$/i,
266
+ /\.html$/i,
267
+ /\.json$/i,
268
+ /\.md$/i,
269
+ /\.svg$/i,
270
+ /\.png$/i,
271
+ /\.jpg$/i,
272
+ /\.jpeg$/i,
273
+ /\.gif$/i,
274
+ /\.bmp$/i,
275
+ /\.ico$/i,
276
+ /\.lock$/i,
277
+ /\.log$/i,
278
+ /\/test\//i,
279
+ /\/tests\//i,
280
+ /\/spec\//i
281
+ ];
282
+
283
+ return ignoredPatterns.some((regex) => regex.test(filePath));
178
284
  }
179
285
  }
180
286
 
@@ -0,0 +1,180 @@
1
+ /**
2
+ * C060 Main Analyzer - Do not override superclass methods and ignore critical logic
3
+ * Primary: Preserve important behavior or lifecycle logic defined in the superclass to ensure correctness and prevent silent errors.
4
+ * Fallback: Regex-based for all other cases
5
+ */
6
+
7
+ const C060SymbolBasedAnalyzer = require('./symbol-based-analyzer');
8
+
9
+ class C060Analyzer {
10
+ constructor(options = {}) {
11
+ if (process.env.SUNLINT_DEBUG) {
12
+ console.log(`🔧 [C060] Constructor called with options:`, !!options);
13
+ console.log(`🔧 [C060] Options type:`, typeof options, Object.keys(options || {}));
14
+ }
15
+
16
+ this.ruleId = 'C060';
17
+ this.ruleName = 'Do not override superclass methods and ignore critical logic';
18
+ this.description = 'Preserve important behavior or lifecycle logic defined in the superclass to ensure correctness and prevent silent errors.';
19
+ this.semanticEngine = options.semanticEngine || null;
20
+ this.verbose = options.verbose || false;
21
+
22
+ // Configuration
23
+ this.config = {
24
+ useSymbolBased: true, // Primary approach
25
+ fallbackToRegex: false, // Only when symbol fails completely
26
+ symbolBasedOnly: false // Can be set to true for pure mode
27
+ };
28
+
29
+ // Initialize both analyzers
30
+ try {
31
+ this.symbolAnalyzer = new C060SymbolBasedAnalyzer(this.semanticEngine);
32
+ if (process.env.SUNLINT_DEBUG) {
33
+ console.log(`🔧 [C060] Symbol analyzer created successfully`);
34
+ }
35
+ } catch (error) {
36
+ console.error(`🔧 [C060] Error creating symbol analyzer:`, error);
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Initialize with semantic engine
42
+ */
43
+ async initialize(semanticEngine = null) {
44
+ if (semanticEngine) {
45
+ this.semanticEngine = semanticEngine;
46
+ }
47
+ this.verbose = semanticEngine?.verbose || false;
48
+
49
+ // Initialize both analyzers
50
+ await this.symbolAnalyzer.initialize(semanticEngine);
51
+
52
+ // Ensure verbose flag is propagated
53
+ this.symbolAnalyzer.verbose = this.verbose;
54
+
55
+ if (this.verbose) {
56
+ console.log(`🔧 [C060 Hybrid] Analyzer initialized - verbose: ${this.verbose}`);
57
+ }
58
+ }
59
+
60
+ async analyze(files, language, options = {}) {
61
+ if (process.env.SUNLINT_DEBUG) {
62
+ console.log(`🔧 [C060] analyze() method called with ${files.length} files, language: ${language}`);
63
+ }
64
+
65
+ const violations = [];
66
+
67
+ for (const filePath of files) {
68
+ try {
69
+ if (process.env.SUNLINT_DEBUG) {
70
+ console.log(`🔧 [C060] Processing file: ${filePath}`);
71
+ }
72
+
73
+ const fileViolations = await this.analyzeFile(filePath, options);
74
+ violations.push(...fileViolations);
75
+
76
+ if (process.env.SUNLINT_DEBUG) {
77
+ console.log(`🔧 [C060] File ${filePath}: Found ${fileViolations.length} violations`);
78
+ }
79
+ } catch (error) {
80
+ console.warn(`❌ [C060] Analysis failed for ${filePath}:`, error.message);
81
+ }
82
+ }
83
+
84
+ if (process.env.SUNLINT_DEBUG) {
85
+ console.log(`🔧 [C060] Total violations found: ${violations.length}`);
86
+ }
87
+
88
+ return violations;
89
+ }
90
+
91
+ async analyzeFile(filePath, options = {}) {
92
+ if (process.env.SUNLINT_DEBUG) {
93
+ console.log(`🔧 [C060] analyzeFile() called for: ${filePath}`);
94
+ }
95
+
96
+ // 1. Try Symbol-based analysis first (primary)
97
+ if (this.config.useSymbolBased &&
98
+ this.semanticEngine?.project &&
99
+ this.semanticEngine?.initialized) {
100
+ try {
101
+ if (process.env.SUNLINT_DEBUG) {
102
+ console.log(`🔧 [C060] Trying symbol-based analysis...`);
103
+ }
104
+ const sourceFile = this.semanticEngine.project.getSourceFile(filePath);
105
+ if (sourceFile) {
106
+ if (process.env.SUNLINT_DEBUG) {
107
+ console.log(`🔧 [C060] Source file found, analyzing with symbol-based...`);
108
+ }
109
+ const violations = await this.symbolAnalyzer.analyzeFileWithSymbols(filePath, { ...options, verbose: options.verbose });
110
+
111
+ // Mark violations with analysis strategy
112
+ violations.forEach(v => v.analysisStrategy = 'symbol-based');
113
+
114
+ if (process.env.SUNLINT_DEBUG) {
115
+ console.log(`✅ [C060] Symbol-based analysis: ${violations.length} violations`);
116
+ }
117
+ return violations; // Return even if 0 violations - symbol analysis completed successfully
118
+ } else {
119
+ if (process.env.SUNLINT_DEBUG) {
120
+ console.log(`⚠️ [C060] Source file not found in project`);
121
+ }
122
+ }
123
+ } catch (error) {
124
+ console.warn(`⚠️ [C060] Symbol analysis failed: ${error.message}`);
125
+ // Continue to fallback
126
+ }
127
+ } else {
128
+ if (process.env.SUNLINT_DEBUG) {
129
+ console.log(`🔄 [C060] Symbol analysis conditions check:`);
130
+ console.log(` - useSymbolBased: ${this.config.useSymbolBased}`);
131
+ console.log(` - semanticEngine: ${!!this.semanticEngine}`);
132
+ console.log(` - semanticEngine.project: ${!!this.semanticEngine?.project}`);
133
+ console.log(` - semanticEngine.initialized: ${this.semanticEngine?.initialized}`);
134
+ console.log(`🔄 [C060] Symbol analysis unavailable, using regex fallback`);
135
+ }
136
+ }
137
+
138
+ if (options?.verbose) {
139
+ console.log(`🔧 [C060] No analysis methods succeeded, returning empty`);
140
+ }
141
+ return [];
142
+ }
143
+
144
+ async analyzeFileBasic(filePath, options = {}) {
145
+ console.log(`🔧 [C060] analyzeFileBasic() called for: ${filePath}`);
146
+ console.log(`🔧 [C060] semanticEngine exists: ${!!this.semanticEngine}`);
147
+ console.log(`🔧 [C060] symbolAnalyzer exists: ${!!this.symbolAnalyzer}`);
148
+
149
+ try {
150
+ // Try symbol-based analysis first
151
+ if (this.semanticEngine?.isSymbolEngineReady?.() &&
152
+ this.semanticEngine.project) {
153
+
154
+ if (this.verbose) {
155
+ console.log(`🔍 [C060] Using symbol-based analysis for ${filePath}`);
156
+ }
157
+
158
+ const violations = await this.symbolAnalyzer.analyzeFileBasic(filePath, options);
159
+ return violations;
160
+ }
161
+ } catch (error) {
162
+ if (this.verbose) {
163
+ console.warn(`⚠️ [C060] Symbol analysis failed: ${error.message}`);
164
+ }
165
+ }
166
+ }
167
+
168
+ /**
169
+ * Methods for compatibility with different engine invocation patterns
170
+ */
171
+ async analyzeFileWithSymbols(filePath, options = {}) {
172
+ return this.analyzeFile(filePath, options);
173
+ }
174
+
175
+ async analyzeWithSemantics(filePath, options = {}) {
176
+ return this.analyzeFile(filePath, options);
177
+ }
178
+ }
179
+
180
+ module.exports = C060Analyzer;
@@ -0,0 +1,50 @@
1
+ {
2
+ "id": "C060",
3
+ "name": "C060_do_not_override_superclass_methods_and_ignore_critical_logic",
4
+ "category": "architecture",
5
+ "description": "C060 - Do not override superclass methods and ignore critical logic",
6
+ "severity": "warning",
7
+ "enabled": true,
8
+ "semantic": {
9
+ "enabled": true,
10
+ "priority": "high",
11
+ "fallback": "heuristic"
12
+ },
13
+ "patterns": {
14
+ "include": [
15
+ "**/*.js",
16
+ "**/*.ts",
17
+ "**/*.jsx",
18
+ "**/*.tsx"
19
+ ],
20
+ "exclude": [
21
+ "**/*.test.*",
22
+ "**/*.spec.*",
23
+ "**/*.mock.*",
24
+ "**/test/**",
25
+ "**/tests/**",
26
+ "**/spec/**"
27
+ ]
28
+ },
29
+ "options": {
30
+ "strictMode": false,
31
+ "allowedDbMethods": [],
32
+ "repositoryPatterns": [
33
+ "*Repository*",
34
+ "*Repo*",
35
+ "*DAO*",
36
+ "*Store*"
37
+ ],
38
+ "servicePatterns": [
39
+ "*Service*",
40
+ "*UseCase*",
41
+ "*Handler*",
42
+ "*Manager*"
43
+ ],
44
+ "complexityThreshold": {
45
+ "methodLength": 200,
46
+ "cyclomaticComplexity": 5,
47
+ "nestedDepth": 3
48
+ }
49
+ }
50
+ }
@@ -0,0 +1,220 @@
1
+ /**
2
+ * C060 Symbol-based Analyzer - Advanced Do not scatter hardcoded constants throughout the logic
3
+ * Purpose: The rule prevents scattering hardcoded constants throughout the logic. Instead, constants should be defined in a single place to improve maintainability and readability.
4
+ */
5
+
6
+ const { SyntaxKind } = require('ts-morph');
7
+
8
+ class C060SymbolBasedAnalyzer {
9
+ constructor(semanticEngine = null) {
10
+ this.ruleId = 'C060';
11
+ this.ruleName = 'Do not override superclass methods (Symbol-Based)';
12
+ this.semanticEngine = semanticEngine;
13
+ this.verbose = false;
14
+
15
+ // === Ignore Configuration ===
16
+ this.ignoredClasses = [
17
+ "React.Component", // React components
18
+ "React.PureComponent",
19
+ "BaseMock", // Custom test mocks
20
+ ];
21
+
22
+ this.ignoredMethods = [
23
+ "render", // React render
24
+ "componentDidMount", // Some lifecycle hooks
25
+ "componentWillUnmount",
26
+ ];
27
+
28
+ this.ignoredFilePatterns = [
29
+ /node_modules/,
30
+ /\.d\.ts$/,
31
+ /\.test\.ts$/,
32
+ ];
33
+ }
34
+
35
+ async initialize(semanticEngine = null) {
36
+ if (semanticEngine) {
37
+ this.semanticEngine = semanticEngine;
38
+ }
39
+ this.verbose = semanticEngine?.verbose || false;
40
+
41
+ if (process.env.SUNLINT_DEBUG) {
42
+ console.log(`🔧 [C060 Symbol-Based] Analyzer initialized, verbose: ${this.verbose}`);
43
+ }
44
+ }
45
+
46
+ async analyzeFileBasic(filePath, options = {}) {
47
+ // This is the main entry point called by the hybrid analyzer
48
+ return await this.analyzeFileWithSymbols(filePath, options);
49
+ }
50
+
51
+ async analyzeFileWithSymbols(filePath, options = {}) {
52
+ const violations = [];
53
+
54
+ // Enable verbose mode if requested
55
+ const verbose = options.verbose || this.verbose;
56
+
57
+ if (!this.semanticEngine?.project) {
58
+ if (verbose) {
59
+ console.warn('[C060 Symbol-Based] No semantic engine available, skipping analysis');
60
+ }
61
+ return violations;
62
+ }
63
+
64
+ if (this.shouldIgnoreFile(filePath)) {
65
+ if (verbose) console.log(`[${this.ruleId}] Ignoring ${filePath}`);
66
+ return violations;
67
+ }
68
+
69
+ if (verbose) {
70
+ console.log(`🔍 [C060 Symbol-Based] Starting analysis for ${filePath}`);
71
+ }
72
+
73
+ try {
74
+ const sourceFile = this.semanticEngine.project.getSourceFile(filePath);
75
+ if (!sourceFile) {
76
+ return violations;
77
+ }
78
+
79
+ const classDeclarations = sourceFile.getClasses();
80
+ for (const classDeclaration of classDeclarations) {
81
+ const classViolations = this.analyzeClass(classDeclaration, filePath, verbose);
82
+ violations.push(...classViolations);
83
+ }
84
+
85
+ if (verbose) {
86
+ console.log(`🔍 [C060 Symbol-Based] Total violations found: ${violations.length}`);
87
+ }
88
+
89
+ return violations;
90
+ } catch (error) {
91
+ if (verbose) {
92
+ console.warn(`[C060 Symbol-Based] Analysis failed for ${filePath}:`, error.message);
93
+ }
94
+
95
+ return violations;
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Analyze one class for override violations
101
+ */
102
+ analyzeClass(classDeclaration, filePath, verbose) {
103
+ const violations = [];
104
+ const baseClass = classDeclaration.getBaseClass();
105
+ if (!baseClass) return violations;
106
+
107
+ // Check if this class should be ignored
108
+ if (this.shouldIgnoreClass(baseClass)) {
109
+ if (verbose) {
110
+ console.log(`[${this.ruleId}] Skipping ignored base class: ${baseClass.getName()}`);
111
+ }
112
+ return violations;
113
+ }
114
+
115
+ const baseMethods = this.collectBaseMethods(baseClass);
116
+
117
+ for (const method of classDeclaration.getMethods()) {
118
+ // Skip ignored methods
119
+ if (this.shouldIgnoreMethod(method)) {
120
+ if (verbose) {
121
+ console.log(`[${this.ruleId}] Skipping ignored method: ${method.getName()}`);
122
+ }
123
+ continue;
124
+ }
125
+
126
+ if (baseMethods.has(method.getName())) {
127
+ const violation = this.checkMethodOverride(method, classDeclaration, filePath, verbose);
128
+ if (violation) {
129
+ violations.push(violation);
130
+ }
131
+ }
132
+ }
133
+
134
+ return violations;
135
+ }
136
+
137
+ /**
138
+ * Collect all method names from base class
139
+ */
140
+ collectBaseMethods(baseClass) {
141
+ return new Set(baseClass.getMethods().map((method) => method.getName()));
142
+ }
143
+
144
+ /**
145
+ * Check if overridden method properly calls super.method()
146
+ */
147
+ checkMethodOverride(method, classDeclaration, filePath, verbose) {
148
+ const body = method.getBodyText();
149
+ if (!body) return null;
150
+
151
+ const methodName = method.getName();
152
+ const baseClass = classDeclaration.getBaseClass();
153
+ if (!baseClass) return null;
154
+ const baseClassName = baseClass.getName();
155
+
156
+ // 1 Get all CallExpressions in the method body
157
+ const calls = method.getDescendantsOfKind(SyntaxKind.CallExpression);
158
+
159
+ // 2 Check for super.method() or BaseClass.prototype.method.call(this)
160
+ const hasSuperCall = calls.some((call) => {
161
+ const expression = call.getExpression().getText();
162
+ return expression === `super.${methodName}`;
163
+ });
164
+
165
+ const hasBaseCall = calls.some((call) => {
166
+ const expression = call.getExpression().getText();
167
+ return expression === `${baseClassName}.prototype.${methodName}.call`;
168
+ });
169
+
170
+ if (!hasSuperCall && !hasBaseCall) {
171
+ const violation = {
172
+ ruleId: this.ruleId,
173
+ severity: 'warning',
174
+ message: `Overridden method '${method.getName()}' in '${classDeclaration.getName()}' does not call 'super.${method.getName()}()', potentially skipping lifecycle/resource logic.`,
175
+ source: this.ruleId,
176
+ file: filePath,
177
+ line: method.getStartLineNumber(),
178
+ column: method.getStart() - method.getStartLinePos(),
179
+ description: `[SYMBOL-BASED] Overriding methods should call the superclass implementation to ensure proper behavior.`,
180
+ suggestion: `Add a call to 'super.${method.getName()}()' within the method body.`,
181
+ category: 'best-practices',
182
+ };
183
+
184
+ if (verbose) {
185
+ console.log(
186
+ `⚠️ [${this.ruleId}] Violation in ${filePath}: Method '${method.getName()}' overrides superclass method but does not call super.`
187
+ );
188
+ }
189
+
190
+ return violation;
191
+ }
192
+
193
+ return null;
194
+ }
195
+
196
+ /**
197
+ * Ignore logic for classes
198
+ */
199
+ shouldIgnoreClass(baseClass) {
200
+ const baseName = baseClass.getName();
201
+ return this.ignoredClasses.includes(baseName);
202
+ }
203
+
204
+ /**
205
+ * Ignore logic for methods
206
+ */
207
+ shouldIgnoreMethod(method) {
208
+ const methodName = method.getName();
209
+ return this.ignoredMethods.includes(methodName);
210
+ }
211
+
212
+ /**
213
+ * Ignore files based on patterns
214
+ */
215
+ shouldIgnoreFile(filePath) {
216
+ return this.ignoredFilePatterns.some((pattern) => pattern.test(filePath));
217
+ }
218
+ }
219
+
220
+ module.exports = C060SymbolBasedAnalyzer;
package/rules/index.js CHANGED
@@ -64,6 +64,7 @@ const commonRules = {
64
64
  C048: loadRule('common', 'C048_no_bypass_architectural_layers'),
65
65
  C052: loadRule('common', 'C052_parsing_or_data_transformation'),
66
66
  C047: loadRule('common', 'C047_no_duplicate_retry_logic'),
67
+ C060: loadRule('common', 'C060_no_override_superclass'),
67
68
  };
68
69
 
69
70
  // 🔒 Security Rules (S-series) - Ready for migration