|
| 1 | +# Coding Conventions |
| 2 | + |
| 3 | +<!-- This file documents project-specific coding standards for exhort-javascript-api. --> |
| 4 | + |
| 5 | +## Language and Framework |
| 6 | + |
| 7 | +- **Primary Language**: JavaScript (ES modules, `"type": "module"` in package.json) |
| 8 | +- **TypeScript**: Configuration present but code is primarily JavaScript with JSDoc |
| 9 | +- **Node.js**: Requires Node >= 20.0.0, npm >= 11.5.1 |
| 10 | +- **CLI**: `yargs` for command-line argument parsing |
| 11 | +- **Parsing Libraries**: `fast-xml-parser`, `fast-toml`, `smol-toml`, `tree-sitter-requirements` |
| 12 | + |
| 13 | +## Code Style |
| 14 | + |
| 15 | +- **Linter**: ESLint with recommended config + editorconfig + import plugins |
| 16 | +- **Indentation**: Tabs (4 spaces for YAML/Markdown) |
| 17 | +- **Line endings**: LF |
| 18 | +- **Max line length**: 100 (120 for Markdown) |
| 19 | +- **Charset**: UTF-8, final newline, trim trailing whitespace |
| 20 | +- **Import ordering** (ESLint enforced): builtin, external, internal, parent, sibling, index — alphabetical within groups |
| 21 | +- **Strict equality**: `eqeqeq: ["warn", "always", {"null": "never"}]` |
| 22 | +- **Curly braces**: Required (`curly: "warn"`) |
| 23 | +- **No throw literals**: `no-throw-literal: "warn"` |
| 24 | +- **No Prettier** — ESLint + EditorConfig handle formatting |
| 25 | + |
| 26 | +## Naming Conventions |
| 27 | + |
| 28 | +- **Classes**: PascalCase with underscore-separated language names (`Java_maven`, `Base_java`, `Javascript_npm`) |
| 29 | +- **Files**: snake_case for providers (`base_java.js`, `javascript_npm.js`, `python_pip.js`) |
| 30 | +- **Test files**: `*.test.js` suffix (`analysis.test.js`, `provider.test.js`) |
| 31 | +- **Functions/Methods**: camelCase (`provideComponent()`, `provideStack()`, `validateLockFile()`) |
| 32 | +- **Variables**: camelCase (`manifestPath`, `backendUrl`) |
| 33 | +- **Constants**: UPPER_SNAKE_CASE (`ecosystem_maven`, `DEFAULT_WORKSPACE_DISCOVERY_IGNORE`) |
| 34 | +- **Private class fields**: `#` prefix (`#manifest`, `#cmd`, `#ecosystem`) |
| 35 | +- **Protected methods**: `_` prefix (`_lockFileName()`, `_cmdName()`, `_listCmdArgs()`) |
| 36 | + |
| 37 | +## File Organization |
| 38 | + |
| 39 | +``` |
| 40 | +src/ |
| 41 | +├── index.js # Main export |
| 42 | +├── cli.js # CLI entry point |
| 43 | +├── analysis.js # API request handling |
| 44 | +├── provider.js # Provider matching logic |
| 45 | +├── workspace.js # Workspace discovery |
| 46 | +├── tools.js # Utilities |
| 47 | +├── sbom.js # SBOM handling |
| 48 | +├── cyclone_dx_sbom.js # CycloneDX SBOM generation |
| 49 | +├── providers/ # Ecosystem providers |
| 50 | +│ ├── base_java.js |
| 51 | +│ ├── base_javascript.js |
| 52 | +│ ├── java_maven.js |
| 53 | +│ ├── javascript_npm.js |
| 54 | +│ ├── python_pip.js |
| 55 | +│ ├── rust_cargo.js |
| 56 | +│ └── processors/ # Specialized processors |
| 57 | +├── license/ # License detection |
| 58 | +└── oci_image/ # OCI image analysis |
| 59 | +
|
| 60 | +test/ |
| 61 | +├── analysis.test.js |
| 62 | +├── provider.test.js |
| 63 | +├── tools.test.js |
| 64 | +└── providers/ # Provider-specific tests |
| 65 | +``` |
| 66 | + |
| 67 | +## Provider Implementation |
| 68 | + |
| 69 | +### `_getDependencyData()` directory parameters |
| 70 | + |
| 71 | +`Base_pyproject._createSbom()` resolves two directories before calling `_getDependencyData()`: |
| 72 | + |
| 73 | +- **`manifestDir`** — the directory containing the `pyproject.toml` being analyzed (`path.dirname(manifest)`). This is always the project directory. |
| 74 | +- **`workspaceDir`** — the directory containing the lock file, found by `_findLockFileDir()` walking up from `manifestDir`. Falls back to `manifestDir` when no lock file is found. |
| 75 | + |
| 76 | +The base class calls `_getDependencyData(manifestDir, workspaceDir, parsed, opts)` with both directories. Subclasses must implement this method with the same 4-parameter signature. |
| 77 | + |
| 78 | +**Lock-file-based providers** (uv, poetry) use `workspaceDir` for lock-file-related operations (parsing, resolving workspace dependencies). They may also use `manifestDir` for running package manager commands that need to execute in the project directory. |
| 79 | + |
| 80 | +**Non-lock-file providers** (pip) must use `manifestDir` for command execution because tools like `pip install .` run relative to the project directory where `pyproject.toml` lives. Since these providers have no lock file, `_findLockFileDir()` returns `null` and `workspaceDir` equals `manifestDir` — but providers must still accept the `workspaceDir` parameter to match the base class signature. |
| 81 | + |
| 82 | +**Guidance for new providers:** |
| 83 | + |
| 84 | +- Always declare all four parameters: `(manifestDir, workspaceDir, parsed, opts)` |
| 85 | +- Use `manifestDir` when running package manager commands that operate on the project (e.g., `pip install .`, `poetry show`) |
| 86 | +- Use `workspaceDir` when reading or resolving lock file contents |
| 87 | +- If your provider does not use a lock file, prefix the unused parameter with `_` (e.g., `_workspaceDir`) to signal that it is intentionally ignored |
| 88 | + |
| 89 | +## Error Handling |
| 90 | + |
| 91 | +- **Throw Error objects**: `throw new Error("message")`, `throw new TypeError("message")` |
| 92 | +- **No custom error classes** — uses built-in `Error` and `TypeError` |
| 93 | +- **HTTP errors**: Check `resp.status`, throw with status code and response text |
| 94 | +- **Async errors**: Bubble up naturally via async/await (no blanket try-catch) |
| 95 | +- **Validation errors**: Thrown early with descriptive context (manifest type, lock file) |
| 96 | + |
| 97 | +## Testing Conventions |
| 98 | + |
| 99 | +- **Framework**: Mocha with TDD UI (`suite()` / `test()`) |
| 100 | +- **Assertions**: Chai with `expect()` syntax |
| 101 | +- **Mocking**: Sinon for stubs; MSW (Mock Service Worker) for HTTP mocking |
| 102 | +- **Module mocking**: `esmock` with experimental loader |
| 103 | +- **Coverage**: C8 with 82% line coverage requirement |
| 104 | +- **Test patterns**: `expect(res).to.deep.equal(...)`, `expect(() => ...).to.throw('message')` |
| 105 | +- **Higher-order setup**: Functions like `interceptAndRun()` for test setup/teardown |
| 106 | + |
| 107 | +## Commit Messages |
| 108 | + |
| 109 | +- Likely Conventional Commits format |
| 110 | +- DCO (Developer Certificate of Origin) required |
| 111 | +- Semantic versioning (`0.3.0` in package.json) |
| 112 | + |
| 113 | +## Dependencies |
| 114 | + |
| 115 | +- **Package manager**: npm with `package-lock.json` |
| 116 | +- **Module system**: ES modules with explicit `.js` extensions in relative imports |
| 117 | +- **Import convention**: `import fs from 'node:fs'` (node: protocol for built-ins) |
| 118 | +- **Environment variables**: Prefixed with `TRUSTIFY_DA_` (e.g., `TRUSTIFY_DA_MVN_PATH`, `TRUSTIFY_DA_TOKEN`, `TRUSTIFY_DA_DEBUG`) |
| 119 | +- **Multi-ecosystem support**: npm, pnpm, yarn, Maven, Gradle, pip, cargo, Go modules, Docker/Podman |
0 commit comments