Skip to content
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
34 changes: 30 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ matrixai-lint --fix
| `--user-config` | Uses detected `eslint.config.[js,mjs,cjs,ts]` from the project root if found |
| `--eslint-config <path>` | Explicitly use a custom ESLint config file |
| `--eslint <targets>` | ESLint targets (files, roots, or globs); implies ESLint domain selection |
| `--shell <targets>` | Shell targets (files, roots, or globs) used to derive shell search roots |
| `--shell <targets>` | Shell targets (files, roots, or globs); implies shell domain selection |
| `--domain <id...>` | Run only selected domains (`eslint`, `shell`, `markdown`) |
| `--skip-domain <id...>` | Skip selected domains (`eslint`, `shell`, `markdown`) |
| `--list-domains` | Print available domains and short descriptions, then exit 0 |
Expand All @@ -66,6 +66,32 @@ Domain selection behavior:
- File paths and glob patterns are reduced to search roots, then `*.sh` files
are discovered under those roots.

#### Targeted workflows

- Only ESLint on a subset of files:

```sh
matrixai-lint --eslint "src/**/*.{ts,tsx}" --domain eslint
```

- Only shell scripts under specific roots:

```sh
matrixai-lint --shell scripts packages/*/scripts
```

- Markdown only:

```sh
matrixai-lint --domain markdown
```

- Mixed scoped run (ESLint + shell only):

```sh
matrixai-lint --eslint "src/**/*.{ts,tsx}" --shell scripts
```

#### Examples

```sh
Expand Down Expand Up @@ -98,7 +124,7 @@ export default config;

```js
// eslint.config.js
import matrixai from '@matrixai/lint/configs/js.js';
import matrixai from '@matrixai/lint/configs/eslint.js';

export default matrixai;
```
Expand Down Expand Up @@ -146,8 +172,8 @@ Supported imports:

- `@matrixai/lint`: named export `config`; types `MatrixAILintCfg`,
`RawMatrixCfg`, `CLIOptions`.
- `@matrixai/lint/configs/js.js`: default export of the ESLint Flat Config array
(same shape as `config`).
- `@matrixai/lint/configs/eslint.js`: default export of the ESLint Flat Config
array (same shape as `config`).
- `@matrixai/lint/configs/prettier.config.js`: reusable Prettier options object.

The exported `config` is intended as a composable base preset for downstream
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@matrixai/lint",
"version": "0.2.12",
"version": "0.3.0",
"author": "Roger Qiu",
"description": "Org wide custom eslint rules",
"license": "Apache-2.0",
Expand Down Expand Up @@ -38,7 +38,7 @@
"tsx": "tsx",
"lint": "npm run prepare && node ./dist/bin/lint.js --domain eslint shell markdown --eslint \"{src,scripts,tests}/**/*.{js,mjs,ts,mts,jsx,tsx}\" --shell src scripts tests",
"lintfix": "npm run prepare && node ./dist/bin/lint.js --fix --domain eslint shell markdown --eslint \"{src,scripts,tests}/**/*.{js,mjs,ts,mts,jsx,tsx}\" --shell src scripts tests",
"lint-shell": "find ./src ./tests ./scripts -type f -regextype posix-extended -regex '.*\\.(sh)' -exec shellcheck {} +",
"lint-shell": "npm run prepare && node ./dist/bin/lint.js --domain shell --shell src scripts tests",
"docs": "shx rm -rf ./docs && typedoc --gitRevision master --tsconfig ./tsconfig.build.json --out ./docs src",
"test": "node ./scripts/test.mjs"
},
Expand Down
71 changes: 28 additions & 43 deletions plans/refactor/PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@
Refactor `matrixai-lint` into a domain-oriented lint engine that:

- Performs smart auto-discovery (project relevance) independently from tool availability.
- Distinguishes built-in npm-based capabilities (for example ESLint/Prettier) from optional external tools (for example ShellCheck, nixfmt).
- Distinguishes built-in npm-based capabilities (for example ESLint/Prettier) from optional external tools (for example ShellCheck).
- Supports explicit domain selection (include/exclude/only) with consistent target semantics.
- Provides clean extension points for adding new domains.
- Provides clear built-in domain boundaries and downstream ESLint extensibility via config composition.

## Non-goals

- No behavior-changing implementation in this epic document (this is design/spec only).
- No changes to the shipped ESLint ruleset content (rule tuning is out of scope).
- No attempt to fully replace ESLint/Prettier/ShellCheck/nixfmt; this project orchestrates them.
- No attempt to fully replace ESLint/Prettier/ShellCheck; this project orchestrates them.

## Constraints / invariants

Expand Down Expand Up @@ -82,7 +82,6 @@ Examples of relevance signals:
- ESLint (JS/TS): any `*.{js,mjs,cjs,jsx,ts,tsx,mts,cts,json}` in the target set.
- Markdown/Prettier: any `*.{md,mdx}` in the target set (plus `README.md` by convention).
- ShellCheck: any `*.sh` in the target set.
- Nixfmt: any `*.nix` in the target set.

Relevance must be computed against the *effective target set* (CLI paths/globs + config defaults), not against the whole filesystem blindly.

Expand All @@ -93,7 +92,7 @@ Relevance must be computed against the *effective target set* (CLI paths/globs +
Availability is domain-specific:

- **Built-in npm-based** domains (ESLint, Prettier) are expected to be available whenever `@matrixai/lint` is installed.
- **External optional** domains (ShellCheck, nixfmt) may not exist in downstream environments.
- **External optional** domains (ShellCheck) may not exist in downstream environments.

### Decision matrix

Expand All @@ -109,17 +108,17 @@ This model explicitly distinguishes "capability exists" from "domain matters".

---

## Proposed domain/plugin architecture
## Proposed domain architecture

### High-level components

- **Lint engine**: parses CLI/config, computes target set, selects domains, orchestrates execution, aggregates results.
- **Domain plugins**: encapsulate discovery, tool checks, execution, and reporting for one domain.
- **Domain modules**: encapsulate discovery, tool checks, execution, and reporting for one domain.
- **Capability registry**: standardizes dependency checks (npm module resolution vs external binary lookup).

### Domain plugin interface (conceptual)
### Domain module interface (conceptual)

Each domain plugin should define:
Each domain module should define:

- `id`: stable string identifier (for example `eslint`, `shellcheck`).
- `discover(ctx)`: returns relevance result and default target patterns for this domain.
Expand All @@ -144,9 +143,6 @@ The engine is responsible for:
3. **shellcheck**
- External optional tool.
- Lints `*.sh`.
4. **nixfmt**
- External optional tool.
- Formats/checks `*.nix`.

### Mermaid: selection and execution pipeline

Expand Down Expand Up @@ -186,7 +182,7 @@ Dependencies are classified at evaluation time:
### Policy rules

- **Auto-discovery must never fail the command** due solely to missing optional external tools.
- **Explicit domain selection must be strict**: if a user asks for `shellcheck` or `nixfmt`, and the tool is missing, exit non-zero.
- **Explicit domain selection must be strict**: if a user asks for `shellcheck`, and the tool is missing, exit non-zero.
- Provide an actionable error message (what is missing, how the tool was detected, how to install).

---
Expand Down Expand Up @@ -240,7 +236,7 @@ Nixfmt-specific:

---

## Config schema proposal (v2) + examples + extension points
## Config schema proposal (v2) + examples

### File discovery

Expand Down Expand Up @@ -279,26 +275,16 @@ Continue to support `matrixai-lint-config.json`, but evolve it to a versioned sc
"tool": {
"command": "shellcheck"
}
},
"nixfmt": {
"enabled": "auto",
"targets": ["**/*.nix"],
"tool": {
"command": "nixfmt"
}
}
},
"plugins": [
{ "module": "./lint-domains/custom-domain.mjs" }
]
}
}
```

Notes:

- `enabled` supports: `auto` | `on` | `off`.
- `plugins` is the extension point: additional domain modules can be loaded to register domains.
- Domain `tool.command` allows a configurable executable name/path for external tools.
- Extensibility for downstream ESLint behavior is via ESLint Flat Config composition from the shipped preset (`@matrixai/lint/configs/eslint.js`) and CLI config selection flags such as `--eslint-config` and `--user-config`.

### Backward compatibility (v1)

Expand Down Expand Up @@ -334,7 +320,6 @@ Define a stable default domain order for readability and compatibility, while al
1. `eslint`
2. `shellcheck`
3. `markdown-prettier`
4. `nixfmt`

### Exit codes

Expand Down Expand Up @@ -366,15 +351,15 @@ If downstream requires a single non-zero code, document that any non-zero is fai

Assume the project contains `src/**/*.ts`, `docs/**/*.md`, `scripts/foo.sh`, and `nix/shell.nix`.

| Invocation | Domain selection intent | Expected executed domains | If `shellcheck` missing | If `nixfmt` missing |
| --- | --- | --- | --- | --- |
| `matrixai-lint` | auto | eslint, markdown-prettier, shellcheck, nixfmt (if relevant) | shellcheck skipped with warning | nixfmt skipped with warning |
| `matrixai-lint --fix` | auto + fix | eslint (fix), markdown-prettier (write), shellcheck (check), nixfmt (format) | shellcheck skipped with warning | nixfmt skipped with warning |
| `matrixai-lint --domain eslint` | explicit | eslint only | n/a | n/a |
| `matrixai-lint --domain shellcheck` | explicit | shellcheck only | fail (missing tool) | n/a |
| `matrixai-lint --skip-domain shellcheck` | auto minus one | eslint, markdown-prettier, nixfmt | n/a | nixfmt skipped with warning |
| `matrixai-lint docs` | auto scoped | markdown-prettier (and maybe eslint if JS files under docs) | likely not relevant | likely not relevant |
| `matrixai-lint --eslint "src/**/*.ts"` | implicit eslint targeting | eslint (explicit targets) + other auto domains unless skipped | shellcheck still auto (unless targets exclude it) | nixfmt still auto (unless targets exclude it) |
| Invocation | Domain selection intent | Expected executed domains | If `shellcheck` missing |
| --- | --- | --- | --- |
| `matrixai-lint` | auto | eslint, markdown-prettier, shellcheck (if relevant) | shellcheck skipped with warning |
| `matrixai-lint --fix` | auto + fix | eslint (fix), markdown-prettier (write), shellcheck (check) | shellcheck skipped with warning |
| `matrixai-lint --domain eslint` | explicit | eslint only | n/a |
| `matrixai-lint --domain shellcheck` | explicit | shellcheck only | fail (missing tool) |
| `matrixai-lint --skip-domain shellcheck` | auto minus one | eslint, markdown-prettier | n/a |
| `matrixai-lint docs` | auto scoped | markdown-prettier (and maybe eslint if JS files under docs) | likely not relevant |
| `matrixai-lint --eslint "src/**/*.ts"` | implicit eslint targeting | eslint (explicit targets) + other auto domains unless skipped | shellcheck still auto (unless targets exclude it) |
| `matrixai-lint --eslnt` | invalid option typo | none | n/a | n/a |

Notes:
Expand All @@ -397,17 +382,17 @@ Notes:
### Suggested rollout phases

1. **Phase 1: Internal refactor (no CLI changes)**
- Introduce the domain abstraction internally and re-implement current behavior using plugins.
- Introduce the domain abstraction internally and re-implement current behavior using built-in domains.
- Preserve existing fixed sequencing as the default order.
- Remove correctness hazards while preserving user-facing defaults:
- enforce single final aggregation path (no early success return masking failures)
- normalize thrown domain errors into unified reporting.

2. **Phase 2: Smart detection and missing-tool policy**
- Implement relevance vs availability model.
- Apply optional-tool skip semantics by default.
- Align multi-tsconfig target derivation with parser project configuration.
- Fix include-pattern normalization so extension-bearing entries remain valid.
2. **Phase 2: Smart detection and missing-tool policy** *(complete)*
- Implemented relevance vs availability model.
- Applied optional-tool skip semantics by default.
- Aligned multi-tsconfig target derivation with parser project configuration (lint targets and ESLint parser projects now share the canonical tsconfig set).
- Fixed include-pattern normalization so extension-bearing entries remain valid.

3. **Phase 3: New CLI for domain selection**
- Add `--domain`, `--skip-domain`, `--list-domains`, `--explain`.
Expand All @@ -420,7 +405,7 @@ Notes:
- Continue supporting v1 config as a subset mapping to the eslint domain.

5. **Phase 5: Extension point hardening**
- Stabilize a plugin loading mechanism and document the domain plugin API.
- Stabilize built-in domain extension surfaces and document downstream ESLint composition guidance.
- Expand CI matrix/scenarios to cover domain-only invocations not exercised by current repo scripts.

### Risks and mitigations
Expand Down
17 changes: 9 additions & 8 deletions plans/refactor/STATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

- First implementation slice for CLI execution semantics has been delivered.
- The target architecture is described in [`PLAN.md`](PLAN.md).
- Multi-tsconfig targeting alignment is complete; lint target derivation and ESLint parser projects now share the same canonical tsconfig set.

## Implemented in full multi-tsconfig targeting alignment slice

Expand All @@ -22,7 +23,7 @@
- only extensionless include entries are expanded with supported ESLint extensions
- malformed glob synthesis paths from blanket suffixing are eliminated
- Completed parser-project/target alignment across ESLint runtime paths:
- [`src/configs/js.ts`](../../src/configs/js.ts) continues to source parser project from resolved tsconfig list
- [`src/configs/eslint.ts`](../../src/configs/eslint.ts) continues to source parser project from resolved tsconfig list
- [`src/utils.ts`](../../src/utils.ts) now injects parser `project` override from the same resolved config used for target derivation
- [`src/domains/eslint.ts`](../../src/domains/eslint.ts) detection now derives default scope from the same multi-tsconfig union logic
- Added regression coverage for multi-tsconfig alignment:
Expand All @@ -38,10 +39,10 @@
- normalized output provides a single resolved shape for CLI/engine consumers
- Updated runtime consumers to use the dedicated config module:
- [`src/utils.ts`](../../src/utils.ts)
- [`src/configs/js.ts`](../../src/configs/js.ts)
- [`src/configs/eslint.ts`](../../src/configs/eslint.ts)
- Expanded public exports for coherent extensibility:
- [`package.json`](../../package.json) includes `./configs/*.js` typed/import exports
- canonical downstream preset import is `@matrixai/lint/configs/js.js`
- canonical downstream preset import is `@matrixai/lint/configs/eslint.js`
- reusable downstream config import path available for `@matrixai/lint/configs/prettier.config.js`
- Updated tests and docs for the new boundary:
- adjusted programmatic export test in [`tests/index.test.ts`](../../tests/index.test.ts)
Expand Down Expand Up @@ -149,7 +150,7 @@
- Implemented changes:
- replaced [`src/configs/prettier.config.mjs`](../../src/configs/prettier.config.mjs) with [`src/configs/prettier.config.js`](../../src/configs/prettier.config.js)
- updated imports and runtime references in:
- [`src/configs/js.ts`](../../src/configs/js.ts)
- [`src/configs/eslint.ts`](../../src/configs/eslint.ts)
- [`src/bin/lint.ts`](../../src/bin/lint.ts)
- Rationale:
- the file is consumed directly at runtime by both ESLint config composition and Prettier CLI `--config`
Expand Down Expand Up @@ -183,7 +184,7 @@ Notes on resolved items:

## Next actions

- Continue phased implementation from [`PLAN.md`](PLAN.md):
- move toward domain/plugin architecture internals
- align config evolution with v2 schema and precedence
- extend domain coverage and execution diagnostics (`--explain`, registry-style reporting)
- Final cleanup for release:
- polish docs for domain/target selection and public import paths
- align scripts to canonical domain-targeting CLI usage
- cut release (version bump, build, test, lintfix)
File renamed without changes.
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export { default as config } from './configs/js.js';
export { default as config } from './configs/eslint.js';
export type {
CLIOptions,
LintDomain,
Expand Down
2 changes: 1 addition & 1 deletion src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ async function runESLint({
logger: Logger;
}): Promise<boolean> {
const dirname = path.dirname(url.fileURLToPath(import.meta.url));
const defaultConfigPath = path.resolve(dirname, './configs/js.js');
const defaultConfigPath = path.resolve(dirname, './configs/eslint.js');
const lintConfig = resolvedConfig ?? resolveLintConfig();
const parserProjectOverride = {
languageOptions: {
Expand Down
2 changes: 1 addition & 1 deletion tests/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import eslintConfigDefault from '#configs/js.js';
import eslintConfigDefault from '#configs/eslint.js';
import { config } from '#index.js';

describe('index exports', () => {
Expand Down
Loading