You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
fix(validate-license): make the rule run, and match the conventional MIT line - #192
Independent of #191 and #193: this branch is based directly on master and carries only the validate-license change, so it can be merged on its own and in any order.
What was wrong
validate-license could not report anything in configs.recommended, and would not have matched much if it had.
It never ran. It lived in recommendedPluginRulesConfigBase, which is only spread into the **/*.{js,cjs,mjs,jsx} and **/*.{ts,cts,mts,tsx} blocks, so no LICENSE could ever match. On the same fixture, ESLint previously answered "File ignored because no matching configuration was supplied" for LICENSE, and now reports.
The copyright regex missed the standard MIT line. It required Copyright (C) <year>[-<year>] by <holder>. The conventional line, Copyright (c) 2025 Dynalist Inc. (this repo's own LICENSE), has a lowercase marker and no " by ", so it matched nowhere and the rule passed silently on every repo using the standard MIT text.
What changes
LICENSE, LICENSE.md and LICENSE.txt get their own file-scoped block in the recommended config, read with a new plugin-contributed language, obsidianmd/plain-text (lib/plainTextLanguage.ts, built on @eslint/plugin-kit's TextSourceCodeBase, with one Line node per line and CRLF handled).
The rule is taken out of the JS/TS base block.
It's a language rather than a parser on purpose. languageOptions.parser is merged by key, so a config object that sets a parser without restricting files replaces it for every file. With a parser, the README's own example shape (...recommended followed by a global { languageOptions: { parser: tseslint.parser, ... } }) made LICENSE reach the TypeScript parser and fail fatally ("was not found by the project service because the extension for the file (``) is non-standard"). A language can only be replaced by another `language`, so that can't happen. I checked this across seven config shapes.
@eslint/plugin-kit is now a declared dependency. ESLint core already depended on it, so it was in the tree before.
lib/plainTextParser.ts is removed. It existed only for this rule, could only be reached by deep import, and was never wired up.
The copyright regex now accepts lowercase (c), the copyright sign and an optional " by ".
index.d.ts declares the plugin's languages export.
Tests
tests/validateLicense.test.ts covers the new copyright forms and the plain-text language.
tests/recommendedConfig.test.ts gets two regression guards:
a "file-scoped rules" block that resolves the config for LICENSE (this would have caught the scoping defect);
a block that lints LICENSE through a config carrying a global parser override and asserts no fatal error (this would have caught the parser-clobbering one).
Docs
LICENSE is matched by name, so ESLint has to be pointed at the project root. An eslint src lint script never visits it, and the check silently does nothing.
The README and docs/configuration.md now say so, under a new "Files linted beyond your source" section.
That section also covers a licence file under another name and explains why the plugin contributes a language rather than a parser.
The scanner-equivalent config switched this rule off inside a block globbed to **/*.{ts,...,jsx}, which LICENSE can't match. That "off" moves to a files: ["LICENSE", "LICENSE.md", "LICENSE.txt"] block.
#191 fixes the same scoping defect for validate-manifest. The two PRs share no code, and neither needs the other, but they edit the same few spots, so whichever merges second will need a small textual conflict resolution in:
lib/index.ts (each removes its own rule from the base block and adds its own file-scoped block),
Disable no-irregular-whitespace for PlainTextSourceCode LICENSE linting
docs/configuration.md:337
This snippet is used with the globally applied eslint.configs.recommended shown above, so it also enables no-irregular-whitespace for LICENSE. That core rule calls sourceCode.getAllComments() during setup, but PlainTextSourceCode does not provide that JavaScript-specific API, causing linting to fail before validate-license runs. Disable it here as the built-in LICENSE block and the custom-filename example do.
…MIT line
The rule could not report anything in `configs.recommended`, and would not have
matched much if it had.
It lived in `recommendedPluginRulesConfigBase`, which is only spread into the
`**/*.{js,cjs,mjs,jsx}` and `**/*.{ts,cts,mts,tsx}` blocks, so no LICENSE could
ever match. Measured before and after on the same fixture: ESLint previously
answered "File ignored because no matching configuration was supplied" for
LICENSE, and now reports.
LICENSE has no extension and is not code, so it also needs something that can
read it. That is now a LANGUAGE -- `obsidianmd/plain-text`, contributed by the
plugin -- rather than the `PlainTextParser` this repo carried but never wired up.
The distinction is load-bearing, not cosmetic. `languageOptions.parser` is merged
by key, so a config object that sets a parser WITHOUT restricting its `files`
replaces it for every file. With a parser, this config:
export default defineConfig([
...obsidianmd.configs.recommended,
{ languageOptions: { parser: tseslint.parser, parserOptions: { ... } } },
]);
made LICENSE reach the TypeScript parser and fail:
Parsing error: LICENSE was not found by the project service because the
extension for the file (``) is non-standard.
That is a fatal error on a file the user never asked to lint, from a config shape
the README's own example invites. A `language` is only replaced by another
`language`, so it cannot happen -- the same reason `package.json` is immune, since it
uses `language: "json/json"`. Verified
across seven config shapes, including a global `parser`, a global
`parser` + `project`, and `extraFileExtensions: [".json"]`.
`lib/plainTextLanguage.ts` implements the language on `@eslint/plugin-kit`'s
`TextSourceCodeBase`, exposing each line as a `Line` node. It handles CRLF, and
its `validateLanguageOptions` deliberately accepts anything, so a stray
`parserOptions` from an unrestricted config block is ignored rather than fatal.
`@eslint/plugin-kit` becomes a declared dependency; it was already in the tree as
a dependency of ESLint core. `lib/plainTextParser.ts` is removed: it existed only
for this rule, was reachable only by deep import, and the rule could never run,
so nothing can be depending on it in a working setup.
The copyright regex required `Copyright (C) <year>[-<year>] by <holder>`. The
conventional MIT line -- `Copyright (c) 2025 Dynalist Inc.`, which is this repo's
own LICENSE -- has a lowercase marker and no " by ", so it matched nowhere and
the rule silently passed on every repo using the standard MIT text. Lowercase
`(c)`, the copyright sign and an optional " by " are now accepted, and
`LICENSE.md` / `LICENSE.txt` are recognised alongside `LICENSE`.
Two regression guards in `tests/recommendedConfig.test.ts`: the "file-scoped
rules" block resolves the config for LICENSE, and a new block lints LICENSE
through a config carrying a global parser override, asserting it does not go
fatal. The first would have caught the scoping defect; the second
would have caught the parser-clobbering one.
Docs. LICENSE is matched by name, so ESLint has to be pointed at the project
root: an `eslint src` lint script never visits it and the check silently does
nothing. README and configuration.md say so, under a new "Files linted beyond
your source" section, and configuration.md also covers a licence file under
another name and explains why the plugin contributes a language rather than a
parser.
The scanner-equivalent config switched this rule off
inside a block globbed to `**/*.{ts,...,jsx}`, which LICENSE cannot match. That
"off" is moved to a `files: ["LICENSE", "LICENSE.md", "LICENSE.txt"]` block.
The rule returned an empty visitor for any file not named LICENSE,
LICENSE.md or LICENSE.txt, so the COPYING block in the configuration
docs silently did nothing. The rule only visits plain-text lines, so
each config block's `files` already decides what it reads.
The documented block also needs `no-irregular-whitespace` off, as the
built-in LICENSE block has: without it ESLint's core rule crashes on
plain text. A test now lints COPYING through that exact block.
…SE snippet
The snippet sits under a config that applies eslint.configs.recommended
to every file, so no-irregular-whitespace also ran on LICENSE. That rule
calls sourceCode.getAllComments(), which the plain-text source code does
not have, so ESLint threw before validate-license ran. Turn it off here,
as the built-in LICENSE block and the custom-filename example already do.
Confirmed and fixed in 6d51eac, and the branch is rebased on master. With eslint.configs.recommended applied to every file, that LICENSE block throws Error while loading rule 'no-irregular-whitespace': sourceCode.getAllComments is not a function before validate-license runs. The snippet now turns no-irregular-whitespace off, the same way the built-in LICENSE block and the COPYING example do. I checked the updated snippet with a real ESLint run and it reports the usual licence findings.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Independent of #191 and #193: this branch is based directly on
masterand carries only thevalidate-licensechange, so it can be merged on its own and in any order.What was wrong
validate-licensecould not report anything inconfigs.recommended, and would not have matched much if it had.recommendedPluginRulesConfigBase, which is only spread into the**/*.{js,cjs,mjs,jsx}and**/*.{ts,cts,mts,tsx}blocks, so noLICENSEcould ever match. On the same fixture, ESLint previously answered "File ignored because no matching configuration was supplied" forLICENSE, and now reports.Copyright (C) <year>[-<year>] by <holder>. The conventional line,Copyright (c) 2025 Dynalist Inc.(this repo's ownLICENSE), has a lowercase marker and no " by ", so it matched nowhere and the rule passed silently on every repo using the standard MIT text.What changes
LICENSE,LICENSE.mdandLICENSE.txtget their own file-scoped block in the recommended config, read with a new plugin-contributed language,obsidianmd/plain-text(lib/plainTextLanguage.ts, built on@eslint/plugin-kit'sTextSourceCodeBase, with oneLinenode per line and CRLF handled).languageOptions.parseris merged by key, so a config object that sets a parser without restrictingfilesreplaces it for every file. With a parser, the README's own example shape (...recommendedfollowed by a global{ languageOptions: { parser: tseslint.parser, ... } }) madeLICENSEreach the TypeScript parser and fail fatally ("was not found by the project service because the extension for the file (``) is non-standard"). Alanguagecan only be replaced by another `language`, so that can't happen. I checked this across seven config shapes.@eslint/plugin-kitis now a declared dependency. ESLint core already depended on it, so it was in the tree before.lib/plainTextParser.tsis removed. It existed only for this rule, could only be reached by deep import, and was never wired up.(c), the copyright sign and an optional " by ".index.d.tsdeclares the plugin'slanguagesexport.Tests
tests/validateLicense.test.tscovers the new copyright forms and the plain-text language.tests/recommendedConfig.test.tsgets two regression guards:LICENSE(this would have caught the scoping defect);LICENSEthrough a config carrying a global parser override and asserts no fatal error (this would have caught the parser-clobbering one).Docs
LICENSEis matched by name, so ESLint has to be pointed at the project root. Aneslint srclint script never visits it, and the check silently does nothing.docs/configuration.mdnow say so, under a new "Files linted beyond your source" section.**/*.{ts,...,jsx}, whichLICENSEcan't match. That "off" moves to afiles: ["LICENSE", "LICENSE.md", "LICENSE.txt"]block.Relation to #191
#191 fixes the same scoping defect for
validate-manifest. The two PRs share no code, and neither needs the other, but they edit the same few spots, so whichever merges second will need a small textual conflict resolution in:lib/index.ts(each removes its own rule from the base block and adds its own file-scoped block),eslint.config.mjs,README.md,docs/configuration.md(both add the "Files linted beyond your source" section: this PR listspackage.jsonandLICENSE, fix(validate-manifest): make the rule run, and stop it over-reporting #191 listspackage.jsonandmanifest.json; the merged text lists all three),tests/recommendedConfig.test.ts(both add a "file-scoped rules" block).In each file the resolution keeps both sides. I've checked that it reproduces exactly the combined tree the stacked version of this PR had.