@konvert7/klint 0.30.0 → 0.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +92 -29
- package/core/arch-comments.ts +14 -0
- package/core/config.schema.ts +2 -2
- package/klint.schema.json +1275 -0
- package/klint.schema.yaml +1001 -0
- package/package.json +8 -6
package/README.md
CHANGED
|
@@ -209,11 +209,23 @@ arch:
|
|
|
209
209
|
|
|
210
210
|
`klint.config.json` is still supported for backwards compatibility.
|
|
211
211
|
|
|
212
|
+
### Editor support
|
|
213
|
+
|
|
214
|
+
The package ships `klint.schema.json` and `klint.schema.yaml`, so the recommended
|
|
215
|
+
setup points at the installed copy:
|
|
216
|
+
|
|
217
|
+
```yaml
|
|
218
|
+
$schema: ./node_modules/@konvert7/klint/klint.schema.json
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
That file is installed alongside klint itself, so it cannot drift — upgrading klint
|
|
222
|
+
upgrades the schema with it. Both the inline `$schema` key above and the
|
|
223
|
+
`# yaml-language-server: $schema=…` modeline work; when both are present the modeline
|
|
224
|
+
wins. `klint.config.json` files are handled by the editor's built-in JSON support.
|
|
225
|
+
|
|
212
226
|
### Pinning the schema version
|
|
213
227
|
|
|
214
|
-
|
|
215
|
-
inside the package, which always matches the installed klint. To pin a specific
|
|
216
|
-
published schema instead, set `$schema` to a version-tagged URL:
|
|
228
|
+
To pin a specific published schema instead, set `$schema` to a version-tagged URL:
|
|
217
229
|
|
|
218
230
|
```yaml
|
|
219
231
|
$schema: https://raw.githubusercontent.com/konvert7/klint/refs/tags/v0.29.0/klint.schema.json
|
|
@@ -221,8 +233,12 @@ $schema: https://raw.githubusercontent.com/konvert7/klint/refs/tags/v0.29.0/klin
|
|
|
221
233
|
|
|
222
234
|
When the tagged version differs from the installed klint, the run reports a
|
|
223
235
|
`klint/schema-version` warning naming both versions and the URL to move to. It is a
|
|
224
|
-
warning, not an error — the exit code is unaffected. Local paths
|
|
225
|
-
|
|
236
|
+
warning, not an error — the exit code is unaffected. Local paths carry no version and
|
|
237
|
+
are never checked, which is why the installed copy above never warns.
|
|
238
|
+
|
|
239
|
+
This check runs when klint runs. Editors validate the config's *shape* against the
|
|
240
|
+
schema, but have no way to compare it against the installed klint, so the warning
|
|
241
|
+
appears in klint's output rather than as an editor diagnostic.
|
|
226
242
|
|
|
227
243
|
`klint --version` prints the installed version.
|
|
228
244
|
|
|
@@ -430,7 +446,7 @@ arch:
|
|
|
430
446
|
in: ["src/**"]
|
|
431
447
|
```
|
|
432
448
|
|
|
433
|
-
Set `countDocComments: true` on either rule to count doc-comments toward the limit as well. Both rules run in the TS and Rust engines; Python (`#`) and
|
|
449
|
+
Set `countDocComments: true` on either rule to count doc-comments toward the limit as well. Both rules run in the TS and Rust engines; Python (`#`), Swift (`//`, `/* */`), and C# (`//`, `/* */`) files are enforced by the Rust engine.
|
|
434
450
|
|
|
435
451
|
Some comments are machinery rather than prose — markers a build or codegen step reads, region pragmas — and counting them makes both limits measure the wrong thing. `ignore` exempts them, on either rule:
|
|
436
452
|
|
|
@@ -446,33 +462,33 @@ An ignored line still counts in the density denominator — it is a physical lin
|
|
|
446
462
|
|
|
447
463
|
### Language Support
|
|
448
464
|
|
|
449
|
-
klint scans TypeScript/JavaScript, Python, Swift, and
|
|
450
|
-
|
|
451
|
-
| | TypeScript / JavaScript | Python | Swift | Rust |
|
|
452
|
-
|
|
453
|
-
| **Engines** | `ts`, `rust` | `rust` | `rust` | `rust` |
|
|
454
|
-
| **Parser** | TypeScript compiler API (TS engine), tree-sitter (Rust engine) | tree-sitter | tree-sitter | tree-sitter |
|
|
455
|
-
| `arch.imports` — static | ✅ | ✅ | ✅ | ✅ `use` |
|
|
456
|
-
| `arch.imports` — dynamic | ✅ `import()` | ✅ `importlib.import_module`, `__import__` | — | — |
|
|
457
|
-
| `arch.imports` — re-exports | ✅ `export … from` | — | ✅ `@_exported import` | ✅ `pub use` |
|
|
458
|
-
| `arch.imports` — multi-target | — | ✅ `import a, b` | — | ✅ `use a::{b, c}` |
|
|
459
|
-
| `type-only: allow` | ✅ `import type` | ✅ `if TYPE_CHECKING:` | — | — |
|
|
460
|
-
| Path aliases | ✅ `tsconfig.json` `paths` | — | — | — |
|
|
461
|
-
| Module resolution | relative paths + alias chains | relative, absolute, and PEP 420 namespace packages | module name against project dirs and file stems | `crate::`/`self::`/`super::` against the directory tree |
|
|
462
|
-
| `deny-packages` | ✅ `/` segments | ✅ `.` segments | ✅ `.` segments, module granularity | ✅ `::` segments |
|
|
463
|
-
| `arch.forbidden` / `singleton` — `pattern` | ✅ | ✅ | ✅ | ✅ |
|
|
464
|
-
| `arch.forbidden` / `singleton` — `jsx-element` | ✅ | — | — | — |
|
|
465
|
-
| `arch.maxLines` | ✅ | ✅ | ✅ | ✅ |
|
|
466
|
-
| `arch.maxCommentDensity` / `maxCommentBlock` | ✅ `//`, `/* */`, `/** */` docs | ✅ `#` | ✅ `//`, `/* */`, `///` and `/** */` docs | ✅ `//`, `/* */`, `///` and `//!` docs |
|
|
467
|
-
| Built-in rules and plugins | ✅ | ✗ | ✗ | ✗ |
|
|
468
|
-
| Custom rules (`klint.rules.ts`) | ✅ | ✗ | ✗ | ✗ |
|
|
469
|
-
| Type-aware rules | ✅ | ✗ | ✗ | ✗ |
|
|
465
|
+
klint scans TypeScript/JavaScript, Python, Swift, Rust, and C#. Every architecture rule is enforced from the same `klint.yaml`, but what a rule can *see* depends on what the language actually has.
|
|
466
|
+
|
|
467
|
+
| | TypeScript / JavaScript | Python | Swift | Rust | C# |
|
|
468
|
+
|---|---|---|---|---|---|
|
|
469
|
+
| **Engines** | `ts`, `rust` | `rust` | `rust` | `rust` | `rust` |
|
|
470
|
+
| **Parser** | TypeScript compiler API (TS engine), tree-sitter (Rust engine) | tree-sitter | tree-sitter | tree-sitter | tree-sitter |
|
|
471
|
+
| `arch.imports` — static | ✅ | ✅ | ✅ | ✅ `use` | ✅ `using` |
|
|
472
|
+
| `arch.imports` — dynamic | ✅ `import()` | ✅ `importlib.import_module`, `__import__` | — | — | — |
|
|
473
|
+
| `arch.imports` — re-exports | ✅ `export … from` | — | ✅ `@_exported import` | ✅ `pub use` | — |
|
|
474
|
+
| `arch.imports` — multi-target | — | ✅ `import a, b` | — | ✅ `use a::{b, c}` | — |
|
|
475
|
+
| `type-only: allow` | ✅ `import type` | ✅ `if TYPE_CHECKING:` | — | — | — |
|
|
476
|
+
| Path aliases | ✅ `tsconfig.json` `paths` | — | — | — | — |
|
|
477
|
+
| Module resolution | relative paths + alias chains | relative, absolute, and PEP 420 namespace packages | module name against project dirs and file stems | `crate::`/`self::`/`super::` against the directory tree | `using` namespace against project namespace declarations |
|
|
478
|
+
| `deny-packages` | ✅ `/` segments | ✅ `.` segments | ✅ `.` segments, module granularity | ✅ `::` segments | ✅ `.` segments |
|
|
479
|
+
| `arch.forbidden` / `singleton` — `pattern` | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
480
|
+
| `arch.forbidden` / `singleton` — `jsx-element` | ✅ | — | — | — | — |
|
|
481
|
+
| `arch.maxLines` | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
482
|
+
| `arch.maxCommentDensity` / `maxCommentBlock` | ✅ `//`, `/* */`, `/** */` docs | ✅ `#` | ✅ `//`, `/* */`, `///` and `/** */` docs | ✅ `//`, `/* */`, `///` and `//!` docs | ✅ `//`, `/* */`, `///` and `/** */` docs |
|
|
483
|
+
| Built-in rules and plugins | ✅ | ✗ | ✗ | ✗ | ✗ |
|
|
484
|
+
| Custom rules (`klint.rules.ts`) | ✅ | ✗ | ✗ | ✗ | ✗ |
|
|
485
|
+
| Type-aware rules | ✅ | ✗ | ✗ | ✗ | ✗ |
|
|
470
486
|
|
|
471
487
|
**✅** supported · **—** the language has no equivalent construct · **✗** not supported yet
|
|
472
488
|
|
|
473
|
-
A dash is not a gap. Swift has no dynamic import expression, no `import type`, and one module per declaration; Rust has no dynamic import and no type-only import
|
|
489
|
+
A dash is not a gap. Swift has no dynamic import expression, no `import type`, and one module per declaration; Rust has no dynamic import and no type-only import; C# has no dynamic `using`, no type-only import, and one namespace per `using`. Those rows can never be filled. The `✗` rows are real limits: the rule set and custom rules are written against the TypeScript AST, and type-aware rules need a type checker that only the TS engine has.
|
|
474
490
|
|
|
475
|
-
Imports are read off the AST in every language, so a specifier written inside a comment or a string is never an import — including nested Swift block comments. Only file discovery differs: the TS engine walks `.ts`/`.tsx`/`.js`/`.jsx`/`.mts`/`.cts`, so Python, Swift, and
|
|
491
|
+
Imports are read off the AST in every language, so a specifier written inside a comment or a string is never an import — including nested Swift block comments. Only file discovery differs: the TS engine walks `.ts`/`.tsx`/`.js`/`.jsx`/`.mts`/`.cts`, so Python, Swift, Rust, and C# files reach klint through the Rust engine, which `auto` mode selects for them automatically.
|
|
476
492
|
|
|
477
493
|
### Python Architecture Checks
|
|
478
494
|
|
|
@@ -613,6 +629,53 @@ not a Cargo build graph or `#[path]` attribute resolver.
|
|
|
613
629
|
Comment budget rules apply to Rust too, where both `///` and `//!` count as
|
|
614
630
|
doc-comments.
|
|
615
631
|
|
|
632
|
+
### C# Architecture Checks
|
|
633
|
+
|
|
634
|
+
The Rust engine can apply architecture rules to C# projects:
|
|
635
|
+
|
|
636
|
+
```yaml
|
|
637
|
+
include: ["src"]
|
|
638
|
+
|
|
639
|
+
rules: {}
|
|
640
|
+
|
|
641
|
+
arch:
|
|
642
|
+
layers:
|
|
643
|
+
web: ["src/Web/**"]
|
|
644
|
+
data: ["src/Data/**"]
|
|
645
|
+
config: ["src/Config/**"]
|
|
646
|
+
|
|
647
|
+
imports:
|
|
648
|
+
- from: web
|
|
649
|
+
deny: data
|
|
650
|
+
message: "Web must use app services, not import the data layer directly"
|
|
651
|
+
- from: web
|
|
652
|
+
deny-packages: ["Newtonsoft"]
|
|
653
|
+
message: "Use System.Text.Json instead of Newtonsoft"
|
|
654
|
+
|
|
655
|
+
forbidden:
|
|
656
|
+
- pattern: "Console.WriteLine("
|
|
657
|
+
in: web
|
|
658
|
+
message: "Use the logger"
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
C# import checks read `using` declarations off the AST, covering `using System;`,
|
|
662
|
+
qualified namespaces such as `using System.Collections.Generic;`, `using static
|
|
663
|
+
System.Math;`, `global using System.Linq;`, and aliases such as `using Json =
|
|
664
|
+
Newtonsoft.Json;` — an alias resolves to its target namespace, not the alias
|
|
665
|
+
name. Because the specifier comes from the parse tree, a `using` written inside a
|
|
666
|
+
`//` or `/* */` comment is not an import.
|
|
667
|
+
|
|
668
|
+
C# namespaces are decoupled from disk layout, so klint indexes the `namespace`
|
|
669
|
+
declarations across the project — block and file-scoped alike — and resolves a
|
|
670
|
+
`using` against that index. Namespaces that no project file declares, such as
|
|
671
|
+
`System`, are ignored by `deny`/`allow` and are the target of `deny-packages`
|
|
672
|
+
instead, which matches on `.` segments so `Newtonsoft` denies `Newtonsoft.Json`
|
|
673
|
+
but never `NewtonsoftShim`. This is architecture enforcement, not a full MSBuild
|
|
674
|
+
or NuGet restore graph.
|
|
675
|
+
|
|
676
|
+
Comment budget rules apply to C# too, where `///` XML doc comments count as
|
|
677
|
+
doc-comments alongside `/** */`.
|
|
678
|
+
|
|
616
679
|
## Built-in Rules
|
|
617
680
|
|
|
618
681
|
| Rule | Type-aware | Description |
|
package/core/arch-comments.ts
CHANGED
|
@@ -15,6 +15,8 @@ function collectComments(file: string, content: string): CommentSpan[] {
|
|
|
15
15
|
const scanner = ts.createScanner(ts.ScriptTarget.Latest, false, variant, content);
|
|
16
16
|
const starts = lineStarts(content);
|
|
17
17
|
const spans: CommentSpan[] = [];
|
|
18
|
+
const substitutionDepths: number[] = [];
|
|
19
|
+
let braceDepth = 0;
|
|
18
20
|
let token = scanner.scan();
|
|
19
21
|
while (token !== ts.SyntaxKind.EndOfFileToken) {
|
|
20
22
|
if (
|
|
@@ -28,6 +30,18 @@ function collectComments(file: string, content: string): CommentSpan[] {
|
|
|
28
30
|
endLine: lineAt(starts, Math.max(start, end - 1)),
|
|
29
31
|
isDoc: isDocComment(content.slice(start, end)),
|
|
30
32
|
});
|
|
33
|
+
} else if (token === ts.SyntaxKind.TemplateHead) {
|
|
34
|
+
substitutionDepths.push(braceDepth);
|
|
35
|
+
} else if (token === ts.SyntaxKind.OpenBraceToken) {
|
|
36
|
+
braceDepth += 1;
|
|
37
|
+
} else if (token === ts.SyntaxKind.CloseBraceToken) {
|
|
38
|
+
if (substitutionDepths.at(-1) === braceDepth) {
|
|
39
|
+
if (scanner.reScanTemplateToken(false) === ts.SyntaxKind.TemplateTail) {
|
|
40
|
+
substitutionDepths.pop();
|
|
41
|
+
}
|
|
42
|
+
} else {
|
|
43
|
+
braceDepth -= 1;
|
|
44
|
+
}
|
|
31
45
|
}
|
|
32
46
|
token = scanner.scan();
|
|
33
47
|
}
|
package/core/config.schema.ts
CHANGED
|
@@ -86,7 +86,7 @@ const ArchImportRuleSchema = z
|
|
|
86
86
|
"Allowlist of layer name(s) / glob(s) the source may import from. Anything not matching `allow` is denied."
|
|
87
87
|
),
|
|
88
88
|
"deny-packages": StringOrStringArray.optional().describe(
|
|
89
|
-
"Package specifier(s) the source is not allowed to import — npm packages, `node:` builtins, Python pip packages and stdlib modules, Swift system frameworks,
|
|
89
|
+
"Package specifier(s) the source is not allowed to import — npm packages, `node:` builtins, Python pip packages and stdlib modules, Swift system frameworks, external Rust crates, and C# namespaces or NuGet packages, which `deny`/`allow` cannot reach because they never resolve to a project file. Matching is per segment: `/` in TypeScript/JavaScript, so `next` also covers `next/headers` while `nextra` is unaffected; `.` in Python, Swift, and C#, so `os` also covers `os.path` while `oscrypto` is unaffected; and `::` in Rust, so `tokio` also covers `tokio::sync::Mutex` while `tokio_util` is unaffected. Python, Swift, Rust, and C# sources are checked by the Rust engine."
|
|
90
90
|
),
|
|
91
91
|
"type-only": z
|
|
92
92
|
.literal("allow")
|
|
@@ -244,7 +244,7 @@ const CountDocCommentsSchema = z
|
|
|
244
244
|
.boolean()
|
|
245
245
|
.optional()
|
|
246
246
|
.describe(
|
|
247
|
-
"Count doc-comments toward the limit. Doc-comments are `/** … */` JSDoc blocks (TS/JS), `///` (Swift), and `///` or `//!` (Rust); ordinary `//`, `/* */`, and `#` comments are always counted. Defaults to false, so documentation is exempt and only explanatory/inline comments are capped. Has no effect on Python: docstrings are string expressions rather than comments, so they never count toward either limit."
|
|
247
|
+
"Count doc-comments toward the limit. Doc-comments are `/** … */` JSDoc blocks (TS/JS), `///` (Swift and C#), and `///` or `//!` (Rust); ordinary `//`, `/* */`, and `#` comments are always counted. Defaults to false, so documentation is exempt and only explanatory/inline comments are capped. Has no effect on Python: docstrings are string expressions rather than comments, so they never count toward either limit."
|
|
248
248
|
);
|
|
249
249
|
|
|
250
250
|
const CommentIgnoreSchema = StringOrStringArray.optional().describe(
|