Skip to content

feat: add a JavaScript/TypeScript ecosystem profile - #124

Merged
lefticus merged 11 commits into
cpp-best-practices:mainfrom
leftibot:javascript-profile
Nov 6, 2026
Merged

lefticus merged 11 commits into
cpp-best-practices:mainfrom
leftibot:javascript-profile

Conversation

@leftibot

@leftibot leftibot commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

Closes #65. First step of Phase 4 in #107.

project_validator/ecosystems/javascript.py judges npm-compatible projects (package.json: npm, pnpm, yarn, bun) and Deno projects (deno.json / deno.jsonc). The ecosystem's label is JavaScript/TypeScript, and it covers two languages: .js .mjs .cjs .jsx count as JavaScript, and .ts .mts .cts .tsx as TypeScript.

Practice Passes when
tests-run *.test.*, *.spec.*, *_test.* (Deno) or __tests__/ files exist, and jest, vitest, mocha, ava, node --test, deno test, bun test, playwright or npm test runs them
coverage c8, nyc, jest --coverage / collectCoverage: true, vitest --coverage / coverage enabled: true, deno test --coverage or node --experimental-test-coverage is used (all of them report branches)
code-formatting CI runs prettier --check, biome format / check / ci, deno fmt --check or dprint check. All of them have a default style
compiler-warnings a linter runs: eslint, biome or deno lint
warnings-as-errors eslint --max-warnings 0, biome ci / --error-on-warnings, or deno lint
dependency-audit npm audit (not npm audit signatures), pnpm audit, yarn (npm) audit or bun audit runs
security-scanning eslint-plugin-security (or @microsoft/eslint-plugin-sdl) is used, or semgrep runs with a JavaScript ruleset
function-limits lizard runs, or eslint runs with max-lines-per-function enabled. It warns when the rule is enabled but eslint never runs
static-analysis eslint, biome lint or deno lint runs
type-checking tsc / vue-tsc, deno check or deno test runs. Bonus: "strict": true or @tsconfig/strictest in tsconfig.json / jsconfig.json, or a Deno project, which is strict unless "strict": false
dependency-management dependencies in package.json (or imports in deno.json) are locked by a committed package-lock.json, npm-shrinkwrap.json, pnpm-lock.yaml, yarn.lock, bun.lock(b) or deno.lock
license the license file matches license in package.json
vcs-ignore / committed-artifacts node_modules/ is ignored. node_modules/, a top-level coverage/ and *.tsbuildinfo must not be committed
platform-matrix CI runs at least two Node.js, Deno or Bun versions (node-version, deno-version, bun-version, node: / denoland/deno: / oven/bun: images)

hardening and ci-build-types do not apply, because JavaScript builds no native binary.

How a project is recognised. Many projects in other languages have a package.json just for their tooling, so that file alone does not make a JavaScript project. It counts only when it names entry points (main, module, exports or bin), or when JavaScript or TypeScript sources exist outside node_modules. deno.json always counts.

Package scripts count as CI. JavaScript tools mostly run through package scripts (npm run lint), not straight from CI. So when CI or a pre-commit hook runs a script, its text counts as evidence too. This covers npm run lint, npm test, pnpm lint, yarn run x, bun run x and deno task x, plus their pre/post scripts and any scripts they call in turn. A script that CI never runs counts for nothing.

Changes outside the profile (please review)

#107 says nothing in project_validator/checks/ should change for a new ecosystem. Two small, language-agnostic changes were needed:

  • Formatter gets an optional texts. Before, code-formatting searched only CI configuration, so npm run format:check would always fail. The default is still CI configuration, so every existing profile behaves exactly as before. The check now reads formatter.texts(context), a one-line change.
  • The JSON-with-comments reader moves from checks/data_wellformed.py to text.strip_json_comments. Profiles can now read tsconfig.json and deno.jsonc with the new evidence.json_value, which mirrors toml_value. data-wellformed behaves the same.

Deviations from #65 (please confirm)

Tests

  • tests/fixtures/well_configured_javascript passes every check that applies to it. tests/fixtures/bare_javascript fails or warns on every JavaScript practice (tests/test_ecosystem_contract.py).
  • tests/test_javascript.py covers recognition, script following, and each rule.
  • Two new layouts in tests/support.py: npm JavaScript and Deno TypeScript.
  • In the existing snapshots, only the "applies to" lists inside SKIP messages change. No CI job is attributed differently. npx alone is not treated as JavaScript usage, because other ecosystems' CI runs jscpd with it.

Local gates all pass:

  • python -W error -m unittest (624 tests)
  • ruff check and format
  • strict mypy
  • branch coverage 99% (new profile 100%)
  • lizard --length 30
  • jscpd: 0 clones
  • project-validator --strict --exclude tests/fixtures .

🤖 Generated with Claude Code

https://claude.ai/code/session_01ByTXNqDWdM7HzezrFtEvQt

npm-compatible (package.json) and Deno (deno.json) projects are judged
by prettier, biome, deno fmt or dprint; branch coverage from c8, nyc,
jest, vitest, deno test or node's test runner; *.test.*, *.spec.* and
__tests__/ files run by any common runner; eslint, biome or deno lint
as the linter, failing on warnings with --max-warnings 0; npm, pnpm,
yarn or bun audits; eslint-plugin-security or semgrep; eslint's
max-lines-per-function; tsc (strict in tsconfig.json) or deno check;
committed lockfiles; package.json's license; an ignored node_modules/;
and the Node.js, Deno and Bun versions CI runs.

A package.json alone is the tooling of many projects in other
languages, so it makes a JavaScript project only when it names entry
points (main, module, exports, bin); JavaScript and TypeScript sources
do too.

JavaScript tooling mostly runs through package scripts, so the scripts
and Deno tasks that CI or a pre-commit hook runs (npm run lint, pnpm
lint, deno task fmt, with their pre/post scripts and the scripts they
run) count as CI configuration. For that, the Formatter rule gets an
optional texts, CI configuration by default, which code-formatting
reads; no other check changes. The JSON-with-comments reader moves from
data-wellformed to text.py, so profiles can read tsconfig.json and
deno.jsonc with evidence.json_value.

Closes cpp-best-practices#65

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ByTXNqDWdM7HzezrFtEvQt
@codecov

codecov Bot commented Nov 6, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.83%. Comparing base (7f93afa) to head (544eddd).

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #124      +/-   ##
==========================================
+ Coverage   98.51%   98.83%   +0.31%     
==========================================
  Files          58       64       +6     
  Lines        2297     2928     +631     
  Branches      231      281      +50     
==========================================
+ Hits         2263     2894     +631     
  Misses         28       28              
  Partials        6        6              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

…adle)

Maven and Gradle projects are judged by Spotless (or fmt-maven-plugin,
google-java-format, ktfmt, ktlint); JaCoCo or Kover branch coverage;
@test methods and Kotest specs under src/test run by mvn or gradle;
javac's -Xlint:all (Kotlin warns by default); -Werror or Kotlin's
allWarningsAsErrors; OWASP dependency-check; Find Security Bugs or
PMD's security rules; Checkstyle MethodLength or detekt LongMethod;
Error Prone, SpotBugs, PMD, Checkstyle or detekt; Gradle dependency
verification, a gradle.lockfile or Maven's --strict-checksums; ignored
target/ (Maven) or build/ (Gradle); and the JDK versions CI runs.

JVM builds wire their tools in: a Maven plugin binds a goal to a phase,
a Gradle plugin adds its task to check. So the build files count as CI
configuration when CI runs the build far enough: any compiling phase
or task for compiler flags, mvn verify or gradle check/build for the
verification plugins. A Maven plugin counts only when an execution
binds the goal.

Two language-agnostic changes support it: DependencyAudit gets the
optional texts that Formatter got (dependency-check bound in pom.xml),
and vcs-ignore judges the BuildOutputs of the build system a project
uses, as committed-artifacts already did (judge_each_ecosystem).
Tests look up checks by name (checks_named) and layouts by name
(run_in_layout).

Closes cpp-best-practices#66

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ByTXNqDWdM7HzezrFtEvQt
Composer projects are judged by PHP-CS-Fixer, PHP_CodeSniffer or Pint;
PHPUnit --path-coverage, which reports branch and path coverage
together (so it earns the path bonus, and line coverage alone counts no
branches); *Test.php files with PHPUnit test cases or Pest tests;
error_reporting E_ALL while testing; PHPUnit failing on warnings,
notices and deprecations; composer audit or roave/security-advisories;
Psalm taint analysis or phpcs-security-audit; PHPMD's
ExcessiveMethodLength; PHPStan, Psalm, PHPMD or Phan; PHPStan or Psalm
as type checkers, strict at PHPStan level 8+ or Psalm errorLevel 1-2;
composer.lock for anything not declared a library; composer.json's
license; an ignored vendor/; and the PHP versions CI runs.

PHP tooling runs through Composer scripts, so the scripts CI runs
(composer test, composer run-script x, @x and @composer x inside a
script) count as CI configuration. Following scripts moves to
evidence.scripts_run, shared with the JavaScript profile.

TestSuite gets the optional texts that Formatter and DependencyAudit
have, which tests-run reads, so tests run through a script count; the
JavaScript profile uses it too. jscpd ignores parenthesised import
blocks, which every full profile shares with the others.

Closes cpp-best-practices#69

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ByTXNqDWdM7HzezrFtEvQt
Bundler projects (Gemfile, *.gemspec) are judged by RuboCop, Standard
or syntax_tree; SimpleCov with enable_coverage :branch; RSpec specs and
Minitest tests run by rspec, rake or rails test; Ruby's own warnings
while testing plus a linter; bundler-audit; brakeman or RuboCop's
Security cops (on by default); RuboCop's Metrics/MethodLength (on by
default, off in Standard); RuboCop, Standard or Reek; Sorbet or Steep,
strict with # typed: strict or D::Ruby.strict; Gemfile.lock for
applications (a gem need not lock); the gemspec's licenses; ignored
.bundle/ and vendor/bundle/; and the Ruby versions and implementations
CI runs.

Ruby projects run their tools through Rake, so the Rakefile and
rakelib/*.rake count as CI configuration when CI runs rake: its
RSpec::Core::RakeTask and RuboCop::RakeTask then run the specs and the
linter.

Closes cpp-best-practices#68

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ByTXNqDWdM7HzezrFtEvQt
leftibot and others added 2 commits November 6, 2026 00:35
MSBuild projects (*.csproj, *.fsproj, *.sln, *.slnx,
Directory.Build.props, global.json) are judged by dotnet format,
CSharpier or Fantomas; coverlet, dotnet-coverage or
Microsoft.Testing.Platform coverage; test projects referencing
Microsoft.NET.Test.Sdk (or MSTest.Sdk) run by dotnet test; AnalysisMode
Recommended or All, EnforceCodeStyleInBuild and Nullable for C#
(<WarnOn> for F#-only projects); TreatWarningsAsErrors; NuGet audit of
every package or dotnet list package --vulnerable; the analyzers'
Security category or Security Code Scan; lizard for C# (F#-only
projects are skipped); analyzer packages such as Roslynator or
FSharpLint; committed packages.lock.json files;
PackageLicenseExpression; ignored bin/ and obj/; and the SDK versions
CI runs.

MSBuild reads its settings from the project files and
Directory.Build.props, so those count as CI configuration when CI
builds, restores or tests. bin/ and obj/ beside a project file are
build output and are never inspected.

Binary hardening (native AOT, CetCompat) is left for a decision, as cpp-best-practices#67
asks, and ci-build-types stays C/C++ and Rust: its requirement includes
sanitizer builds, which .NET does not have.

Closes cpp-best-practices#67

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ByTXNqDWdM7HzezrFtEvQt
SwiftPM (Package.swift) and Xcode (*.xcodeproj/, *.xcworkspace/)
projects are judged by swift-format or SwiftFormat in lint mode; region
coverage from swift test --enable-code-coverage or Xcode's coverage
(Swift's instrumentation counts regions, not branches); XCTest cases or
Swift Testing @test functions run by swift test or xcodebuild test;
SwiftLint plus complete concurrency checking; -warnings-as-errors,
treatAllWarnings(as: .error) or SWIFT_TREAT_WARNINGS_AS_ERRORS;
releases that keep Swift's runtime safety checks (no -Ounchecked or
unchecked exclusivity); SwiftLint's function_body_length; SwiftLint as
the static analyzer; osv-scanner as the audit; Package.resolved for
applications; ignored .build/ (SwiftPM) or xcuserdata/ and DerivedData/
(Xcode); and the Swift and Xcode versions CI runs, each built debug and
release, with and without sanitizers.

Build configurations now include SwiftPM's (debug unless -c release)
and xcodebuild's -configuration, beside CMake build types and Cargo
profiles. The README's "Applies to" cells lose a doubled space.

Closes cpp-best-practices#70

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ByTXNqDWdM7HzezrFtEvQt
feat: add a Swift ecosystem profile (SwiftPM and Xcode)
feat: add a .NET ecosystem profile (C# and F#)
feat: add a Ruby ecosystem profile
feat: add a PHP ecosystem profile
feat: add a JVM ecosystem profile (Java and Kotlin, with Maven and Gradle)
@lefticus
lefticus merged commit 7c10e78 into cpp-best-practices:main Nov 6, 2026
18 checks passed
@leftibot
leftibot deleted the javascript-profile branch November 7, 2026 02:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add a JavaScript/TypeScript ecosystem profile (Node, Deno, Bun)

2 participants