diff --git a/README.md b/README.md index 10a4bb4..39bc4e6 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,7 @@ matrixai-lint --fix | `--user-config` | Uses detected `eslint.config.[js,mjs,cjs,ts]` from the project root if found | | `--eslint-config ` | Explicitly use a custom ESLint config file | | `--eslint ` | ESLint targets (files, roots, or globs); implies ESLint domain selection | +| `--markdown ` | Markdown targets (files, roots, or globs); implies markdown domain selection | | `--shell ` | Shell targets (files, roots, or globs); implies shell domain selection | | `--domain ` | Run only selected domains (`eslint`, `shell`, `markdown`) | | `--skip-domain ` | Skip selected domains (`eslint`, `shell`, `markdown`) | @@ -58,6 +59,9 @@ Domain selection behavior: - `--eslint ...` runs ESLint only. - `--shell ...` runs shell only. - Passing both runs both. +- Passing `--markdown` implies markdown domain selection. + - `--markdown ...` runs markdown only. + - Combined with other target flags, only those targeted domains run. - `shellcheck` is optional only for default auto-run shell execution. - If shell is explicitly requested (`--shell ...` or `--domain shell`), missing `shellcheck` is a failure. @@ -65,6 +69,10 @@ Domain selection behavior: - Directories are used as roots. - File paths and glob patterns are reduced to search roots, then `*.sh` files are discovered under those roots. +- `--markdown` accepts target paths and glob patterns. + - Directories are used as roots. + - File paths and glob patterns are reduced to search roots, then `*.md` / + `*.mdx` files are discovered under those roots. #### Targeted workflows @@ -86,6 +94,12 @@ Domain selection behavior: matrixai-lint --domain markdown ``` +- Markdown only under selected roots: + + ```sh + matrixai-lint --markdown standards templates README.md + ``` + - Mixed scoped run (ESLint + shell only): ```sh @@ -99,6 +113,7 @@ matrixai-lint --fix matrixai-lint --user-config matrixai-lint --eslint-config ./eslint.config.js --fix matrixai-lint --eslint "src/**/*.{ts,tsx}" --shell scripts +matrixai-lint --markdown standards templates README.md matrixai-lint --domain eslint markdown matrixai-lint --skip-domain markdown matrixai-lint --list-domains diff --git a/package-lock.json b/package-lock.json index 4cf2ac2..cdd741e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@matrixai/lint", - "version": "0.3.0", + "version": "0.4.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@matrixai/lint", - "version": "0.3.0", + "version": "0.4.0", "license": "Apache-2.0", "dependencies": { "@eslint/compat": "^1.2.5", diff --git a/package.json b/package.json index 492d85d..9a0c8d3 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { "name": "@matrixai/lint", - "version": "0.3.0", + "version": "0.4.0", "author": "Roger Qiu", "description": "Org wide custom eslint rules", "license": "Apache-2.0", "repository": { "type": "git", - "url": "https://github.com/MatrixAI/js-eslint.git" + "url": "git+https://github.com/MatrixAI/js-eslint.git" }, "type": "module", "exports": { @@ -29,7 +29,7 @@ "#*": "./dist/*" }, "bin": { - "matrixai-lint": "./dist/bin/lint.js" + "matrixai-lint": "dist/bin/lint.js" }, "scripts": { "prepare": "tsc -p ./tsconfig.build.json", diff --git a/src/bin/lint.ts b/src/bin/lint.ts index 8f9b9a4..43c81e1 100644 --- a/src/bin/lint.ts +++ b/src/bin/lint.ts @@ -44,6 +44,7 @@ program ) .option('--eslint-config ', 'Path to explicit ESLint config file') .option('--eslint ', 'ESLint targets (files, roots, or globs)') + .option('--markdown ', 'Markdown targets (files, roots, or globs)') .option( '--shell ', 'Shell targets (files, roots, or globs) used to derive shellcheck search roots', @@ -139,6 +140,7 @@ async function main(argv = process.argv) { const explain = Boolean(options.explain); const eslintPatterns: string[] | undefined = options.eslint; + const markdownPatterns: string[] | undefined = options.markdown; const shellPatterns: string[] | undefined = options.shell; const { selectedDomains, explicitlyRequestedDomains, selectionSources } = resolveDomainSelection(options); @@ -197,6 +199,7 @@ async function main(argv = process.argv) { chosenConfig, isConfigValid, eslintPatterns, + markdownPatterns, shellPatterns, }, }); @@ -214,6 +217,7 @@ async function main(argv = process.argv) { chosenConfig, isConfigValid, eslintPatterns, + markdownPatterns, shellPatterns, }, }); diff --git a/src/domains/engine.ts b/src/domains/engine.ts index 76ab4a3..fd6d5de 100644 --- a/src/domains/engine.ts +++ b/src/domains/engine.ts @@ -8,6 +8,7 @@ type LintDomainEngineContext = { isConfigValid: boolean; eslintPatterns?: string[]; shellPatterns?: string[]; + markdownPatterns?: string[]; }; type LintDomainAvailabilityKind = 'required' | 'optional'; diff --git a/src/domains/index.ts b/src/domains/index.ts index 3c85369..90b806a 100644 --- a/src/domains/index.ts +++ b/src/domains/index.ts @@ -29,6 +29,7 @@ function resolveDomainSelection(options: CLIOptions): { const hasDomainSelectors = domainFlags.length > 0 || skipDomains.size > 0; const hasExplicitESLintTargets = (options.eslint?.length ?? 0) > 0; const hasExplicitShellTargets = (options.shell?.length ?? 0) > 0; + const hasExplicitMarkdownTargets = (options.markdown?.length ?? 0) > 0; const explicitlyRequestedDomains = new Set(domainFlags); const selectionSources = new Map(); @@ -38,6 +39,9 @@ function resolveDomainSelection(options: CLIOptions): { if (hasExplicitShellTargets) { explicitlyRequestedDomains.add('shell'); } + if (hasExplicitMarkdownTargets) { + explicitlyRequestedDomains.add('markdown'); + } let selectedDomains: Set; @@ -48,7 +52,9 @@ function resolveDomainSelection(options: CLIOptions): { } } else if ( !hasDomainSelectors && - (hasExplicitESLintTargets || hasExplicitShellTargets) + (hasExplicitESLintTargets || + hasExplicitShellTargets || + hasExplicitMarkdownTargets) ) { selectedDomains = new Set(); if (hasExplicitESLintTargets) { @@ -59,6 +65,10 @@ function resolveDomainSelection(options: CLIOptions): { selectedDomains.add('shell'); selectionSources.set('shell', 'target-flag'); } + if (hasExplicitMarkdownTargets) { + selectedDomains.add('markdown'); + selectionSources.set('markdown', 'target-flag'); + } } else { selectedDomains = new Set(LINT_DOMAINS); for (const domain of LINT_DOMAINS) { diff --git a/src/domains/markdown.ts b/src/domains/markdown.ts index 52f5cd0..731f517 100644 --- a/src/domains/markdown.ts +++ b/src/domains/markdown.ts @@ -46,8 +46,11 @@ function createMarkdownDomainPlugin({ return { domain: 'markdown', description: 'Format and check Markdown/MDX files with Prettier.', - detect: () => { - const searchPatterns = DEFAULT_MARKDOWN_SEARCH_ROOTS; + detect: ({ markdownPatterns }) => { + const searchPatterns = + markdownPatterns != null && markdownPatterns.length > 0 + ? markdownPatterns + : DEFAULT_MARKDOWN_SEARCH_ROOTS; const searchRoots = resolveSearchRootsFromPatterns(searchPatterns); const matchedFiles = collectMarkdownFilesFromScope(searchRoots); diff --git a/src/types.ts b/src/types.ts index 07a7207..619f3d1 100644 --- a/src/types.ts +++ b/src/types.ts @@ -35,6 +35,7 @@ type CLIOptions = { eslintConfig?: string; eslint?: string[]; shell?: string[]; + markdown?: string[]; domain?: LintDomain[]; skipDomain?: LintDomain[]; listDomains?: boolean; diff --git a/tests/bin/lint.test.ts b/tests/bin/lint.test.ts index e5b4526..551a616 100644 --- a/tests/bin/lint.test.ts +++ b/tests/bin/lint.test.ts @@ -96,6 +96,31 @@ describe('matrixai-lint CLI domain semantics', () => { expect(prettierCalls).toHaveLength(0); }); + test('--markdown no longer triggers eslint/shell domains', async () => { + await fs.promises.mkdir(path.join(dataDir, 'standards'), { + recursive: true, + }); + await fs.promises.writeFile( + path.join(dataDir, 'standards', 'guide.md'), + '# guide\n', + 'utf8', + ); + + await expect( + main(['node', 'matrixai-lint', '--markdown', 'standards']), + ).resolves.toBeUndefined(); + + const shellCalls = capturedExecCalls.filter((c) => c.file === 'shellcheck'); + expect(shellCalls).toHaveLength(0); + + const prettierCalls = capturedExecCalls.filter( + (c) => + c.file === 'prettier' || + c.args.some((arg) => /prettier\.cjs$/.test(arg)), + ); + expect(prettierCalls.length).toBeGreaterThan(0); + }); + test('explicit shell request + missing shellcheck fails', async () => { jest .spyOn(childProcess, 'spawnSync') diff --git a/tests/domains/index.test.ts b/tests/domains/index.test.ts index a0e5a21..7582163 100644 --- a/tests/domains/index.test.ts +++ b/tests/domains/index.test.ts @@ -457,6 +457,34 @@ describe('domain selection', () => { expect(selectionSources.get('eslint')).toBe('target-flag'); }); + test('markdown target flag implies explicit markdown domain request', () => { + const { selectedDomains, explicitlyRequestedDomains, selectionSources } = + resolveDomainSelection({ + fix: false, + userConfig: false, + markdown: ['standards', 'templates', 'README.md'], + }); + + expect([...selectedDomains]).toStrictEqual(['markdown']); + expect([...explicitlyRequestedDomains]).toStrictEqual(['markdown']); + expect(selectionSources.get('markdown')).toBe('target-flag'); + }); + + test('domain flag remains authoritative over markdown target flag', () => { + const { selectedDomains, explicitlyRequestedDomains, selectionSources } = + resolveDomainSelection({ + fix: false, + userConfig: false, + domain: ['eslint'], + markdown: ['standards'], + }); + + expect([...selectedDomains]).toStrictEqual(['eslint']); + expect([...explicitlyRequestedDomains]).toStrictEqual(['eslint']); + expect(selectionSources.get('eslint')).toBe('domain-flag'); + expect(selectionSources.has('markdown')).toBe(false); + }); + test('--domain keeps explicit domains and --skip-domain removes them', () => { const { selectedDomains, explicitlyRequestedDomains, selectionSources } = resolveDomainSelection({