Skip to content

Commit

Permalink
Merge pull request #1 from surenpoghosian/antipatterns
Browse files Browse the repository at this point in the history
[antipatterns] add support for more antipatterns
  • Loading branch information
surenpoghosian authored Aug 23, 2024
2 parents c5fb0ea + 96fd345 commit 2ba1ab9
Show file tree
Hide file tree
Showing 9 changed files with 133 additions and 38 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
dist/
dist/Analyzers/
node_modules/
.node_modules/
built/*
Expand Down
36 changes: 0 additions & 36 deletions dist/ProjectAnalyzer.mjs

This file was deleted.

1 change: 0 additions & 1 deletion dist/ProjectAnalyzer.mjs.map

This file was deleted.

15 changes: 14 additions & 1 deletion src/Analyzers/TypeScriptAnalyzer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,22 @@ import { Report } from '../Reports/Report';
import { Hint } from '../Reports/Hint';
import { CodeDuplicationPattern } from '../Patterns/CodeDuplicationPattern';
import { FileReader } from '../Utils/FileReader';
import { CodeSmellPattern } from '../Patterns/CodeSmellPattern';
import { GodObjectASTPattern } from '../Patterns/GodObjectASTPattern';
import { GodObjectPattern } from '../Patterns/GodeObjectPattern';
import { MagicNumbersPattern } from '../Patterns/MagicNumbersPattern';
import { ShotgunSurgeryPattern } from '../Patterns/ShotgunSurgeryPattern';

export class TypeScriptAnalyzer extends BaseAnalyzer {
private patterns = [new CodeDuplicationPattern()];
private patterns = [
new CodeDuplicationPattern(),
new CodeSmellPattern(),
new GodObjectASTPattern(),
new GodObjectPattern(),
new MagicNumbersPattern(),
new ShotgunSurgeryPattern()
// Add other patterns here...
];

supports(file: string): boolean {
return file.endsWith('.ts');
Expand Down
27 changes: 27 additions & 0 deletions src/Patterns/CodeSmellPattern.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { BasePattern } from './BasePattern';
import { Hint } from '../Reports/Hint';

export class CodeSmellPattern extends BasePattern {
analyze(content: string): Hint[] {
const hints: Hint[] = [];

// Deep Nesting
const deepNestingRegex = /if\s*\([^\)]+\)\s*\{[^]*(if\s*\([^\)]+\)\s*\{[^]*(if\s*\([^\)]+\)\s*\{[^]*\})\})\}/g;
let match: RegExpExecArray | null;
while ((match = deepNestingRegex.exec(content)) !== null) {
hints.push(new Hint(`Possible deep nesting detected. Consider refactoring.`));
}

// Long Methods
const methodRegex = /(?:public|private|protected|static)?\s*(?:async\s+)?(?:function)?\s*\w+\(.*?\)\s*\{[^]*?\}/g;
while ((match = methodRegex.exec(content)) !== null) {
const methodBody = match[0];
const lineCount = methodBody.split('\n').length;
if (lineCount > 50) { // Threshold can be adjusted
hints.push(new Hint(`Possible long method detected with ${lineCount} lines. Consider refactoring.`));
}
}

return hints;
}
}
22 changes: 22 additions & 0 deletions src/Patterns/GodObjectASTPattern.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { BasePattern } from './BasePattern';
import { Hint } from '../Reports/Hint';
import * as ts from 'typescript';

export class GodObjectASTPattern extends BasePattern {
analyze(content: string): Hint[] {
const hints: Hint[] = [];
const sourceFile = ts.createSourceFile('file.ts', content, ts.ScriptTarget.Latest, true);

function analyzeNode(node: ts.Node) {
if (ts.isClassDeclaration(node) && node.members.length > 20) { // Threshold can be adjusted
const className = node.name ? node.name.getText() : 'Unnamed Class';
hints.push(new Hint(`Possible God Object detected: ${className} has too many members.`));
}
node.forEachChild(analyzeNode);
}

analyzeNode(sourceFile);

return hints;
}
}
24 changes: 24 additions & 0 deletions src/Patterns/GodeObjectPattern.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { BasePattern } from './BasePattern';
import { Hint } from '../Reports/Hint';

export class GodObjectPattern extends BasePattern {
analyze(content: string): Hint[] {
const hints: Hint[] = [];
const classRegex = /class\s+\w+\s*\{[^]*?\}/g;
const methodRegex = /(?:public|private|protected|static)?\s*(?:async\s+)?(?:function)?\s*\w+\(.*?\)\s*\{[^]*?\}/g;
const propertyRegex = /(?:public|private|protected|static)?\s*(?:async\s+)?\w+\s*:\s*\w+\s*;/g;

let match: RegExpExecArray | null;
while ((match = classRegex.exec(content)) !== null) {
const classBody = match[0];
const methodCount = (classBody.match(methodRegex) || []).length;
const propertyCount = (classBody.match(propertyRegex) || []).length;

if (methodCount > 10 || propertyCount > 10) { // Threshold can be adjusted
hints.push(new Hint(`Possible God Object detected: Class has ${methodCount} methods and ${propertyCount} properties.`));
}
}

return hints;
}
}
20 changes: 20 additions & 0 deletions src/Patterns/MagicNumbersPattern.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { BasePattern } from './BasePattern';
import { Hint } from '../Reports/Hint';

export class MagicNumbersPattern extends BasePattern {
analyze(content: string): Hint[] {
const hints: Hint[] = [];
const magicNumberRegex = /\b\d+\b/g;
const allowedNumbers = new Set([0, 1]); // Add more allowed numbers if necessary

let match: RegExpExecArray | null;
while ((match = magicNumberRegex.exec(content)) !== null) {
const number = parseInt(match[0], 10);
if (!allowedNumbers.has(number)) {
hints.push(new Hint(`Possible Magic Number detected: "${number}" should be replaced with a named constant.`));
}
}

return hints;
}
}
24 changes: 24 additions & 0 deletions src/Patterns/ShotgunSurgeryPattern.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { BasePattern } from './BasePattern';
import { Hint } from '../Reports/Hint';

export class ShotgunSurgeryPattern extends BasePattern {
analyze(content: string): Hint[] {
const hints: Hint[] = [];
const methodOrPropertyRegex = /\b\w+\b/g;
const occurrences = new Map<string, number>();

let match: RegExpExecArray | null;
while ((match = methodOrPropertyRegex.exec(content)) !== null) {
const name = match[0];
occurrences.set(name, (occurrences.get(name) || 0) + 1);
}

occurrences.forEach((count, name) => {
if (count > 5) { // Threshold can be adjusted
hints.push(new Hint(`Possible Shotgun Surgery detected: "${name}" is modified in ${count} places.`));
}
});

return hints;
}
}

0 comments on commit 2ba1ab9

Please sign in to comment.