Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[antipatterns] add support for more antipatterns #2

Merged
merged 3 commits into from
Aug 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading