Skip to content

Commit

Permalink
Merge pull request #2 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 2ba1ab9 + 6db7c38 commit 8e20553
Show file tree
Hide file tree
Showing 19 changed files with 376 additions and 52 deletions.
21 changes: 19 additions & 2 deletions .github/workflows/release-package.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,23 @@ jobs:
- name: Run tests
run: npm test

publish-npm:
needs: build
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20

- name: Publish to npm
run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

publish-gpr:
needs: build
runs-on: ubuntu-latest
Expand All @@ -38,8 +55,8 @@ jobs:
node-version: 20
registry-url: https://npm.pkg.github.com/

- name: Install dependencies
run: npm install
- name: Configure npm to use GitHub Packages registry
run: npm config set registry https://npm.pkg.github.com/

- name: Publish to GitHub Packages
run: npm publish
Expand Down
29 changes: 28 additions & 1 deletion dist/Analyzers/TypeScriptAnalyzer.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/Analyzers/TypeScriptAnalyzer.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "code-diagnose",
"version": "1.0.1",
"version": "1.1.1",
"main": "dist/index.js",
"bin": {
"analyze-project": "./dist/index.js"
Expand Down
16 changes: 15 additions & 1 deletion src/Analyzers/TypeScriptAnalyzer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ import { GodObjectASTPattern } from '../Patterns/GodObjectASTPattern';
import { GodObjectPattern } from '../Patterns/GodeObjectPattern';
import { MagicNumbersPattern } from '../Patterns/MagicNumbersPattern';
import { ShotgunSurgeryPattern } from '../Patterns/ShotgunSurgeryPattern';
import { LargeClassPattern } from '../Patterns/LargeClassPattern';
import { LazyClassPattern } from '../Patterns/LazyClassPattern';
import { LongParameterListPattern } from '../Patterns/LongParameterListPattern';
import { MiddleManPattern } from '../Patterns/MiddleManPattern';
import { PrimitiveObsessionPattern } from '../Patterns/PrimitiveObsessionPattern';
import { SpeculativeGeneralityPattern } from '../Patterns/SpeculativeGeneralityPattern';
import { SwitchStatementOverusePattern } from '../Patterns/SwitchStatementOverusePattern';

export class TypeScriptAnalyzer extends BaseAnalyzer {
private patterns = [
Expand All @@ -18,7 +25,14 @@ export class TypeScriptAnalyzer extends BaseAnalyzer {
new GodObjectASTPattern(),
new GodObjectPattern(),
new MagicNumbersPattern(),
new ShotgunSurgeryPattern()
new ShotgunSurgeryPattern(),
new LargeClassPattern(),
new LazyClassPattern(),
new LongParameterListPattern(),
new MiddleManPattern(),
new PrimitiveObsessionPattern(),
new SpeculativeGeneralityPattern(),
new SwitchStatementOverusePattern(),
// Add other patterns here...
];

Expand Down
14 changes: 7 additions & 7 deletions src/Patterns/GodeObjectPattern.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,17 @@ 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;
const classRegex = /class\s+(\w+)\s*\{([^]*?)\}/g; // Match class bodies
const methodRegex = /(?:public|private|protected|static)?\s*(?:async\s+)?(?:function|method)\s*\w+\s*\(.*?\)\s*\{[^]*?\}/g;
const propertyRegex = /(?:public|private|protected|static)?\s*\w+\s*:\s*\w+\s*;/g;

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

if (methodCount > 10 || propertyCount > 10) { // Threshold can be adjusted
if (methodCount > 10 || propertyCount > 10) { // Adjust thresholds as needed
hints.push(new Hint(`Possible God Object detected: Class has ${methodCount} methods and ${propertyCount} properties.`));
}
}
Expand Down
24 changes: 24 additions & 0 deletions src/Patterns/LargeClassPattern.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 LargeClassPattern 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*\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 > 15 || propertyCount > 15) { // Threshold can be adjusted
hints.push(new Hint(`Possible Large Class detected: This class has ${methodCount} methods and ${propertyCount} properties. Consider splitting it into smaller classes.`));
}
}

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

export class LazyClassPattern extends BasePattern {
analyze(content: string): Hint[] {
const hints: Hint[] = [];
const classRegex = /class\s+\w+\s*\{[^]*?\}/g;

let match: RegExpExecArray | null;
while ((match = classRegex.exec(content)) !== null) {
const classBody = match[0];
const methodCount = (classBody.match(/(?:public|private|protected|static)?\s*(?:async\s+)?(?:function)?\s*\w+\(.*?\)\s*\{[^]*?\}/g) || []).length;

if (methodCount <= 1) {
hints.push(new Hint(`Possible Lazy Class detected: This class has only ${methodCount} method(s). Consider merging it with another class.`));
}
}

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

export class LongParameterListPattern extends BasePattern {
analyze(content: string): Hint[] {
const hints: Hint[] = [];
const methodRegex = /\w+\(([^)]*)\)\s*\{/g;

let match: RegExpExecArray | null;
while ((match = methodRegex.exec(content)) !== null) {
const params = match[1].split(',').map(param => param.trim());
if (params.length > 5) { // Threshold can be adjusted
hints.push(new Hint(`Possible Long Parameter List detected: Method with ${params.length} parameters. Consider refactoring.`));
}
}

return hints;
}
}
17 changes: 12 additions & 5 deletions src/Patterns/MagicNumbersPattern.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,19 @@ 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
const namedConstantRegex = /const\s+(\w+)\s*=\s*(\d+)/g; // Match named constants

let match: RegExpExecArray | null;
while ((match = magicNumberRegex.exec(content)) !== null) {
const number = parseInt(match[0], 10);
if (!allowedNumbers.has(number)) {
const namedConstants = new Set<string>();
let constantMatch: RegExpExecArray | null;

while ((constantMatch = namedConstantRegex.exec(content)) !== null) {
namedConstants.add(constantMatch[2]); // Add constant values
}

let numberMatch: RegExpExecArray | null;
while ((numberMatch = magicNumberRegex.exec(content)) !== null) {
const number = numberMatch[0];
if (!namedConstants.has(number)) {
hints.push(new Hint(`Possible Magic Number detected: "${number}" should be replaced with a named constant.`));
}
}
Expand Down
16 changes: 16 additions & 0 deletions src/Patterns/MiddleManPattern.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { BasePattern } from './BasePattern';
import { Hint } from '../Reports/Hint';

export class MiddleManPattern extends BasePattern {
analyze(content: string): Hint[] {
const hints: Hint[] = [];
const methodDelegationRegex = /\w+\(\s*\)\s*{\s*return\s+\w+\.\w+\(\);?\s*}/g;

let match: RegExpExecArray | null;
while ((match = methodDelegationRegex.exec(content)) !== null) {
hints.push(new Hint(`Possible Middle Man detected: This method simply delegates its work to another method. Consider removing it.`));
}

return hints;
}
}
24 changes: 24 additions & 0 deletions src/Patterns/PrimitiveObsessionPattern.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 PrimitiveObsessionPattern extends BasePattern {
analyze(content: string): Hint[] {
const hints: Hint[] = [];
const primitiveRegex = /\b(string|number|boolean)\b/g;
const occurrences = new Map<string, number>();

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

occurrences.forEach((count, type) => {
if (count > 10) { // Threshold can be adjusted
hints.push(new Hint(`Possible Primitive Obsession detected: "${type}" is used ${count} times. Consider encapsulating in a custom type.`));
}
});

return hints;
}
}
29 changes: 20 additions & 9 deletions src/Patterns/ShotgunSurgeryPattern.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,29 @@ 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>();
const functionRegex = /function\s+(\w+)\s*\(.*?\)\s*\{([^]*?)\}/g; // Match function bodies
const variableRegex = /\b(\w+)\b\s*=\s*[^;]*;/g; // Match variable assignments

let match: RegExpExecArray | null;
while ((match = methodOrPropertyRegex.exec(content)) !== null) {
const name = match[0];
occurrences.set(name, (occurrences.get(name) || 0) + 1);
const variableUsage = new Map<string, Set<string>>();
let functionMatch: RegExpExecArray | null;

while ((functionMatch = functionRegex.exec(content)) !== null) {
const functionBody = functionMatch[2];
const functionName = functionMatch[1];

let variableMatch: RegExpExecArray | null;
while ((variableMatch = variableRegex.exec(functionBody)) !== null) {
const variableName = variableMatch[1];
if (!variableUsage.has(variableName)) {
variableUsage.set(variableName, new Set());
}
variableUsage.get(variableName)!.add(functionName);
}
}

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.`));
variableUsage.forEach((functions, variable) => {
if (functions.size > 1) { // Adjust threshold as needed
hints.push(new Hint(`Possible Shotgun Surgery detected: "${variable}" is modified in ${functions.size} functions.`));
}
});

Expand Down
19 changes: 19 additions & 0 deletions src/Patterns/SpeculativeGeneralityPattern.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { BasePattern } from './BasePattern';
import { Hint } from '../Reports/Hint';

export class SpeculativeGeneralityPattern extends BasePattern {
analyze(content: string): Hint[] {
const hints: Hint[] = [];
const interfaceRegex = /interface\s+\w+\s*\{[^]*?\}/g;

let match: RegExpExecArray | null;
while ((match = interfaceRegex.exec(content)) !== null) {
const interfaceBody = match[0];
if (interfaceBody.includes('<T>') && !interfaceBody.includes('T used')) { // Simplified check for generic misuse
hints.push(new Hint(`Possible Speculative Generality detected: Interface uses generics without clear necessity.`));
}
}

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

export class SwitchStatementOverusePattern extends BasePattern {
analyze(content: string): Hint[] {
const hints: Hint[] = [];
const switchRegex = /switch\s*\(.*?\)\s*\{[^]*?\}/g;

let match: RegExpExecArray | null;
while ((match = switchRegex.exec(content)) !== null) {
const caseCount = (match[0].match(/case\s+/g) || []).length;
if (caseCount > 5) { // Threshold can be adjusted
hints.push(new Hint(`Possible Switch Statement Overuse detected: This switch statement has ${caseCount} cases. Consider refactoring.`));
}
}

return hints;
}
}
Loading

0 comments on commit 8e20553

Please sign in to comment.