15

Development guide

Build, test, lint, debug, CI, release; how to add a rule or a language

5 min read12 sections

How to build, test, debug and release CleanLens, and how to extend it.

Setup

bash
git clone https://github.com/MUSAALAHMED4/CleanLens.git
cd CleanLens
npm install

Requires Node.js ≥ 20 and Git.

Scripts

CommandWhat it does
npm run buildBuilds dist/extension.js and dist/cli.js
npm run build:extensionExtension bundle only (esbuild, vscode external, CJS, Node platform)
npm run build:cliCLI bundle only (adds the #!/usr/bin/env node banner)
npm run watchRebuild the extension on change, with source maps
npm testAll tests: node --import tsx --test "test/**/*.test.ts"
npm run typechecktsc --noEmit (strict mode)
npm run lintESLint over src/
npm run packageBuild a .vsix with vsce

vscode:prepublish / prepublishOnly build minified bundles automatically before packaging or publishing.

Project layout

text
CleanLens/
├── src/                 source (see 03 — Architecture for the module map)
├── test/                node:test suites (run with tsx, no build needed)
├── dist/                build output (git-ignored)
├── docs/                this documentation + RULES.md + PRIVACY.md
├── media/               icons and screenshots
├── website/             marketing site (separate Next.js app; see 16)
├── .github/workflows/   CI
├── .vscode/             launch + build task for F5 debugging
├── package.json         manifest: extension contributions, bin, scripts
├── tsconfig.json        strict, ES2022, Node16 modules
├── .eslintrc.json       eslint:recommended + @typescript-eslint/recommended
├── README.md / README.ar.md   product page (EN / AR)
├── CHANGELOG.md         release notes
└── Clean_Code_Tracker_Plan.md  original design plan

Debugging

  • Extension: press F5. The Run Extension configuration runs the npm: build task, then opens an Extension Development Host with this extension loaded. Open any Git repository in that window and run the commands.
  • CLI: npm run build:cli && node dist/cli.js /path/to/repo, or use npx tsx src/cli/index.ts /path/to/repo without building.
  • Dashboard HTML: renderDashboardDocument(report, { nonce: "x", standalone: true }) returns a page you can open in a browser.

Tests

The tests use Node's built-in node:test runner and tsx. No VS Code instance is needed: the tested code does not import vscode.

FileCovers
test/jsAnalyzer.test.tsJS rules (debug, empty catch, params, secrets, error handling, names, complexity), Python basics, cross-file duplicates
test/config.test.tsPreset validity, validator errors, preset detection
test/contributors.test.tsActive days, identity merging (all key types and their guards), labels, explicit identities
test/glob.test.tsGlob → RegExp, rooted vs. unrooted patterns, base-name matching
test/scoring.test.tsScore formula properties, confidence levels, ranking threshold, fingerprint stability
test/cliFormat.test.tsText and Markdown output, not-ranked section, --fail-on logic
test/attribution.test.tsLegacy blame parsing and ownership helpers
test/integration.test.tsEnd-to-end on real temporary Git repositories: fair attribution, fixes, whitespace reformat, exclusions, root commit, small contributors

Integration tests create repositories with git init in a temp directory and use an isolated, empty git config, so they do not depend on your global Git settings. Run one file with:

bash
node --import tsx --test test/scoring.test.ts

Continuous integration

.github/workflows/ci.yml runs on pushes to main and on pull requests, on Ubuntu, Windows and macOS × Node 22 and 24:

npm ci → typecheck → lint → test → build → vsce package.

The Ubuntu / Node 24 job uploads the .vsix as an artifact. Windows is included on purpose: path separators, CRLF output and git.exe resolution have caused real bugs before (see the changelog).

Releasing

  1. Bump version in package.json; add a CHANGELOG.md entry.
  2. Make sure CI is green.
  3. npm run build && npx vsce package --no-dependencies → cleanlens-<version>.vsix.
  4. Publish with npx vsce publish (needs a PAT for publisher cleanlens) or upload the .vsix in the Marketplace publisher portal.
  5. Optionally npm publish for the CLI (same version).
  6. Update EXTENSION_VERSION in website/lib/constants.ts.

The files list in package.json controls what ships (in both the .vsix and the npm package): the two bundles, the icons, README.md, CHANGELOG.md, docs/PRIVACY.md, docs/RULES.md, LICENSE.txt. The other docs in this folder are not shipped; add them to files if you want them in the package.

Coding conventions

  • TypeScript strict. No any unless unavoidable.
  • Only src/extension.ts and src/views/* may import vscode.
  • Analyzers are pure functions: (relPath, text, config) => RawFinding[].
  • Every Git call uses execFile (no shell) and handles failure explicitly.
  • Tunable numbers belong in src/scoring/scoringConfig.ts (scoring) or as named constants at the top of their module.
  • Bump ENGINE_VERSION when a change alters per-commit analysis results.

Adding a rule

Example: a new rule maxLineLength.

  1. Register it in src/configuration/types.ts:
    • add "maxLineLength" to RULE_IDS;
    • add a title in RULE_TITLES (e.g. "Long line");
    • add its category in RULE_CATEGORY (e.g. "structure"). TypeScript will then point out every place that needs the new id.
  2. Give it defaults in BASE_RULES in src/configuration/presets.ts: maxLineLength: { enabled: true, severity: "low", limit: 120 }.
  3. Implement it in jsAnalyzer.ts and/or pyAnalyzer.ts:
    ts
    if (on("maxLineLength")) {
      const max = lim("maxLineLength", 120);
      text.split("\n").forEach((line, i) => {
        if (line.length > max) add("maxLineLength", i + 1, `Line has ${line.length} characters (limit ${max}).`);
      });
    }
    Pass a symbolName when the finding belongs to a function or class; it makes attribution more accurate.
  4. Bump ENGINE_VERSION in scoringConfig.ts.
  5. Test it in test/jsAnalyzer.test.ts (positive and negative cases).
  6. Document it in docs/RULES.md, 06 — Rules reference, and the README rule tables (EN and AR).

Existing config files do not list the new rule, so it is off for them until users add it (a missing rule is disabled). The validator accepts it once it is in RULE_IDS.

Adding a language

  1. Create src/analyzers/<lang>Analyzer.ts exporting analyze<Lang>(filePath, text, config): RawFinding[] and its extensions list. Use ruleEnabled, ruleLimit, ruleOption from model.ts, and reuse secrets.ts / names.ts.
  2. Register it in runAnalyzers.ts: add the extensions to ALL_EXTENSIONS and a branch in analyzerFor(). The HEAD scan, the commit walk and live diagnostics all use analyzerFor(), so nothing else needs wiring.
  3. Check fingerprint.ts strips that language's line-comment syntax (today // and #).
  4. Consider a preset in presets.ts and detection in detectPreset().
  5. Bump ENGINE_VERSION, add tests, update the docs.

Documentation and translations

The docs exist in three languages with the same file names:

text
docs/*.md      English — the source of truth
docs/ar/*.md   Arabic
docs/tr/*.md   Turkish

When you change an English doc, update the two translations in the same change. Rules that keep the website's docs pages (see 16) working:

  • Keep the heading structure identical (same number of headings, same levels, same order). The website gives translated headings the English anchor ids by position, so links and URLs are the same in every language.
  • Links to section anchors use the English ids in every language (e.g. 08-scoring.md#severity-weights).
  • Links to files outside docs/ go one level further up in translations (../../src/… instead of ../src/…).
  • Code blocks, identifiers, rule ids, config keys and CLI output stay in English.
  • If a translation file is missing, the website shows the English one.

Adding a VS Code setting

  1. Declare it under contributes.configuration.properties in package.json.
  2. Read it in readSettings() in src/extension.ts.
  3. If it maps to a config key, add it to ExtensionSettings and applySettings() in src/core/analyzeRepository.ts. Prefer undefined when the user has not set it, so the config file is not overridden by a default.
  4. Document it in 10 — Configuration reference.