home / skills / aj-geddes / useful-ai-prompts / static-code-analysis

static-code-analysis skill

/skills/static-code-analysis

This skill helps you enforce code quality and security with linting, formatting, and scanning tools across projects.

npx playbooks add skill aj-geddes/useful-ai-prompts --skill static-code-analysis

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
10.9 KB
---
name: static-code-analysis
description: Implement static code analysis with linters, formatters, and security scanners to catch bugs early. Use when enforcing code standards, detecting security vulnerabilities, or automating code review.
---

# Static Code Analysis

## Overview

Use automated tools to analyze code without executing it, catching bugs, security issues, and style violations early.

## When to Use

- Enforcing coding standards
- Security vulnerability detection
- Bug prevention
- Code review automation
- CI/CD pipelines
- Pre-commit hooks
- Refactoring assistance

## Implementation Examples

### 1. **ESLint Configuration**

```javascript
// .eslintrc.js
module.exports = {
  extends: [
    'eslint:recommended',
    'plugin:@typescript-eslint/recommended',
    'plugin:security/recommended'
  ],
  plugins: ['@typescript-eslint', 'security', 'import'],
  rules: {
    'no-console': ['warn', { allow: ['error', 'warn'] }],
    'no-unused-vars': 'error',
    'prefer-const': 'error',
    'eqeqeq': ['error', 'always'],
    'no-eval': 'error',
    'security/detect-object-injection': 'warn',
    'security/detect-non-literal-regexp': 'warn',
    '@typescript-eslint/no-explicit-any': 'warn',
    '@typescript-eslint/explicit-function-return-type': 'error',
    'import/order': ['error', {
      'groups': ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'],
      'newlines-between': 'always'
    }]
  }
};
```

### 2. **Python Linting (pylint + mypy)**

```python
# .pylintrc
[MASTER]
ignore=venv,.git,__pycache__
jobs=4

[MESSAGES CONTROL]
disable=
    missing-docstring,
    too-few-public-methods

[FORMAT]
max-line-length=100
max-module-lines=1000

[DESIGN]
max-args=5
max-locals=15
max-returns=6
max-branches=12
max-statements=50
```

```python
# mypy.ini
[mypy]
python_version = 3.10
warn_return_any = True
warn_unused_configs = True
disallow_untyped_defs = True
disallow_incomplete_defs = True
check_untyped_defs = True
disallow_untyped_calls = True
warn_redundant_casts = True
warn_unused_ignores = True
strict_equality = True
```

### 3. **Pre-commit Hooks**

```yaml
# .pre-commit-config.yaml
repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.4.0
    hooks:
      - id: trailing-whitespace
      - id: end-of-file-fixer
      - id: check-yaml
      - id: check-json
      - id: check-merge-conflict
      - id: detect-private-key

  - repo: https://github.com/pre-commit/mirrors-eslint
    rev: v8.50.0
    hooks:
      - id: eslint
        files: \.[jt]sx?$
        types: [file]

  - repo: https://github.com/psf/black
    rev: 23.9.1
    hooks:
      - id: black

  - repo: https://github.com/PyCQA/pylint
    rev: v3.0.0
    hooks:
      - id: pylint

  - repo: https://github.com/pre-commit/mirrors-mypy
    rev: v1.5.1
    hooks:
      - id: mypy
        additional_dependencies: [types-requests]

  - repo: https://github.com/trufflesecurity/trufflehog
    rev: v3.58.0
    hooks:
      - id: trufflehog
        entry: trufflehog filesystem --directory .
```

### 4. **SonarQube Integration**

```yaml
# sonar-project.properties
sonar.projectKey=my-project
sonar.projectName=My Project
sonar.projectVersion=1.0

sonar.sources=src
sonar.tests=tests
sonar.exclusions=**/node_modules/**,**/*.test.ts

sonar.typescript.lcov.reportPaths=coverage/lcov.info

sonar.qualitygate.wait=true

# Quality gates
sonar.coverage.exclusions=**/*.test.ts
```

```yaml
# .github/workflows/sonar.yml
name: SonarQube Analysis

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  sonar:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
        with:
          fetch-depth: 0

      - name: SonarQube Scan
        uses: sonarsource/sonarqube-scan-action@master
        env:
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
          SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}

      - name: Quality Gate Check
        uses: sonarsource/sonarqube-quality-gate-action@master
        timeout-minutes: 5
        env:
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
```

### 5. **Custom AST Analysis**

```typescript
import * as ts from 'typescript';
import * as fs from 'fs';

interface Issue {
  file: string;
  line: number;
  column: number;
  message: string;
  severity: 'error' | 'warning' | 'info';
  rule: string;
}

class CustomLinter {
  private issues: Issue[] = [];

  lintFile(filePath: string): Issue[] {
    this.issues = [];

    const sourceCode = fs.readFileSync(filePath, 'utf-8');
    const sourceFile = ts.createSourceFile(
      filePath,
      sourceCode,
      ts.ScriptTarget.Latest,
      true
    );

    this.visit(sourceFile, filePath);

    return this.issues;
  }

  private visit(node: ts.Node, filePath: string): void {
    // Check for console.log
    if (
      ts.isCallExpression(node) &&
      ts.isPropertyAccessExpression(node.expression) &&
      node.expression.expression.getText() === 'console' &&
      node.expression.name.getText() === 'log'
    ) {
      const { line, character } = ts.getLineAndCharacterOfPosition(
        node.getSourceFile(),
        node.getStart()
      );

      this.issues.push({
        file: filePath,
        line: line + 1,
        column: character + 1,
        message: 'Unexpected console.log statement',
        severity: 'warning',
        rule: 'no-console'
      });
    }

    // Check for any type
    if (
      ts.isTypeReferenceNode(node) &&
      node.typeName.getText() === 'any'
    ) {
      const { line, character } = ts.getLineAndCharacterOfPosition(
        node.getSourceFile(),
        node.getStart()
      );

      this.issues.push({
        file: filePath,
        line: line + 1,
        column: character + 1,
        message: 'Avoid using any type',
        severity: 'warning',
        rule: 'no-any'
      });
    }

    // Check for long functions
    if (ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node)) {
      const body = node.body;
      if (body && body.getFullText().split('\n').length > 50) {
        const { line, character } = ts.getLineAndCharacterOfPosition(
          node.getSourceFile(),
          node.getStart()
        );

        this.issues.push({
          file: filePath,
          line: line + 1,
          column: character + 1,
          message: 'Function is too long (>50 lines)',
          severity: 'warning',
          rule: 'max-lines-per-function'
        });
      }
    }

    ts.forEachChild(node, child => this.visit(child, filePath));
  }

  formatIssues(issues: Issue[]): string {
    if (issues.length === 0) {
      return 'No issues found.';
    }

    return issues.map(issue =>
      `${issue.file}:${issue.line}:${issue.column} - ${issue.severity}: ${issue.message} (${issue.rule})`
    ).join('\n');
  }
}

// Usage
const linter = new CustomLinter();
const issues = linter.lintFile('./src/example.ts');
console.log(linter.formatIssues(issues));
```

### 6. **Security Scanning**

```typescript
import { exec } from 'child_process';
import { promisify } from 'util';

const execAsync = promisify(exec);

interface SecurityIssue {
  severity: 'critical' | 'high' | 'medium' | 'low';
  title: string;
  description: string;
  file?: string;
  line?: number;
  remediation?: string;
}

class SecurityScanner {
  async scanDependencies(): Promise<SecurityIssue[]> {
    try {
      const { stdout } = await execAsync('npm audit --json');
      const auditResult = JSON.parse(stdout);

      const issues: SecurityIssue[] = [];

      for (const [name, advisory] of Object.entries(auditResult.vulnerabilities || {})) {
        const vuln = advisory as any;

        issues.push({
          severity: vuln.severity,
          title: vuln.via[0]?.title || name,
          description: vuln.via[0]?.url || '',
          remediation: `Update ${name} to ${vuln.fixAvailable || 'latest'}`
        });
      }

      return issues;
    } catch (error) {
      console.error('Dependency scan failed:', error);
      return [];
    }
  }

  async scanSecrets(directory: string): Promise<SecurityIssue[]> {
    const issues: SecurityIssue[] = [];

    // Simple regex-based secret detection
    const patterns = [
      { name: 'API Key', pattern: /api[_-]?key['"]?\s*[:=]\s*['"]([a-zA-Z0-9]{32,})['"]/ },
      { name: 'AWS Key', pattern: /(AKIA[0-9A-Z]{16})/ },
      { name: 'Private Key', pattern: /-----BEGIN (RSA |EC )?PRIVATE KEY-----/ },
      { name: 'Password', pattern: /password['"]?\s*[:=]\s*['"]((?!<%= ).{8,})['"]/ }
    ];

    // Scan files
    const files = this.getFiles(directory);

    for (const file of files) {
      const content = fs.readFileSync(file, 'utf-8');
      const lines = content.split('\n');

      for (let i = 0; i < lines.length; i++) {
        for (const { name, pattern } of patterns) {
          if (pattern.test(lines[i])) {
            issues.push({
              severity: 'critical',
              title: `Potential ${name} detected`,
              description: `Found in ${file}:${i + 1}`,
              file,
              line: i + 1,
              remediation: 'Remove secret and use environment variables'
            });
          }
        }
      }
    }

    return issues;
  }

  private getFiles(dir: string): string[] {
    // Implementation to recursively get files
    return [];
  }

  generateReport(issues: SecurityIssue[]): string {
    let report = '# Security Scan Report\n\n';

    const grouped = issues.reduce((acc, issue) => {
      acc[issue.severity] = acc[issue.severity] || [];
      acc[issue.severity].push(issue);
      return acc;
    }, {} as Record<string, SecurityIssue[]>);

    for (const [severity, items] of Object.entries(grouped)) {
      report += `## ${severity.toUpperCase()} (${items.length})\n\n`;

      for (const issue of items) {
        report += `### ${issue.title}\n`;
        report += `${issue.description}\n`;
        if (issue.remediation) {
          report += `**Remediation:** ${issue.remediation}\n`;
        }
        report += '\n';
      }
    }

    return report;
  }
}

// Usage
const scanner = new SecurityScanner();
const depIssues = await scanner.scanDependencies();
const secretIssues = await scanner.scanSecrets('./src');

const allIssues = [...depIssues, ...secretIssues];
console.log(scanner.generateReport(allIssues));
```

## Best Practices

### ✅ DO
- Run linters in CI/CD
- Use pre-commit hooks
- Configure IDE integration
- Fix issues incrementally
- Document custom rules
- Share configuration across team
- Automate security scanning

### ❌ DON'T
- Ignore all warnings
- Skip linter setup
- Commit lint violations
- Use overly strict rules initially
- Skip security scans
- Disable rules without reason

## Tools

- **JavaScript/TypeScript**: ESLint, TSLint, Prettier
- **Python**: Pylint, Flake8, Black, Bandit
- **Java**: PMD, Checkstyle, SpotBugs
- **Security**: Snyk, Semgrep, Bandit, TruffleHog
- **Multi-language**: SonarQube, CodeQL

## Resources

- [ESLint](https://eslint.org/)
- [SonarQube](https://www.sonarqube.org/)
- [Snyk](https://snyk.io/)
- [OWASP Dependency-Check](https://owasp.org/www-project-dependency-check/)

Overview

This skill implements static code analysis with linters, formatters, and security scanners to catch bugs, style violations, and vulnerabilities early. It provides configurations and examples to integrate tools into pre-commit hooks, CI/CD pipelines, and local developer workflows. The goal is consistent code quality, faster code review, and automated security checks before runtime.

How this skill works

The skill runs a suite of static tools that analyze source code without executing it: linters enforce style and correctness rules, type checkers find type errors, formatters normalize code layout, and security scanners detect vulnerable dependencies and embedded secrets. Integrations include pre-commit hooks for local enforcement, CI jobs for continuous checks, and AST-based custom rules when project-specific inspections are needed. Results are aggregated into readable reports and quality-gate checks stop problematic changes.

When to use it

  • Enforcing team coding standards across a repository
  • Detecting security vulnerabilities and leaked secrets before deployment
  • Automating repetitive parts of code review in pull requests
  • Running checks in CI/CD to prevent regressions
  • Adding fast feedback in pre-commit hooks for developers
  • Supporting large refactors with safety checks

Best practices

  • Run linters and security scans in CI with a quality gate
  • Enforce quick fixes automatically using formatters in pre-commit hooks
  • Start with a pragmatic rule set and tighten rules incrementally
  • Document and share custom rules and configuration across the team
  • Treat warnings as actionable items, not noise to ignore

Example use cases

  • Add ESLint, Prettier, and security plugins to a TypeScript monorepo and block merges on new errors
  • Use pylint and mypy in a Python project with pre-commit hooks to catch typing and style issues locally
  • Integrate SonarQube in CI to report coverage, code smells, and security hotspots with a quality gate
  • Run dependency audits and trufflehog scans to find vulnerable packages and exposed secrets
  • Implement a small AST-based custom linter to enforce domain-specific rules (forbidden APIs, max function length)

FAQ

Will static analysis slow down development?

Local checks can be lightweight with pre-commit hooks and fast linters; heavier scans run in CI so developers get quick feedback while full checks run on push or PR.

How do I avoid overwhelming the team with warnings?

Start with essential rules, suppress or configure noisy checks, and adopt incremental remediation to fix legacy issues over time.