@forinda/kickjs-lint 5.2.4 → 5.2.6

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # @forinda/kickjs-lint
2
2
 
3
- Lint rules for KickJS conventions — DI tokens, plugin/adapter naming, and other framework patterns.
3
+ Lint rules for KickJS conventions: DI tokens made with `createToken<T>()`, and the reserved `kick/` token prefix.
4
4
 
5
5
  ## Install
6
6
 
@@ -8,42 +8,25 @@ Lint rules for KickJS conventions — DI tokens, plugin/adapter naming, and othe
8
8
  pnpm add -D @forinda/kickjs-lint
9
9
  ```
10
10
 
11
- ## CLI
11
+ ## Quick example
12
12
 
13
13
  ```bash
14
- # adopter project — lints `src/`, warns on third-party kick/ squatting
15
- kick-lint
16
-
17
- # framework / first-party packages — lints `packages/`, errors on missing kick/ prefix
18
- kick-lint --first-party
19
-
20
- # custom scope
21
- kick-lint --scope src,libs
14
+ kick-lint # lints src/
15
+ kick-lint --scope src,libs # other folders
22
16
  ```
23
17
 
24
- ## Programmatic
18
+ | Rule | Default | Checks |
19
+ | ----------------------- | ------- | ------------------------------------------------------------ |
20
+ | `di-token-symbol` | `error` | tokens use `createToken<T>()`, not `Symbol(...)` |
21
+ | `token-kick-prefix` | `error` | first-party tokens start with `kick/` (`--first-party` only) |
22
+ | `token-reserved-prefix` | `warn` | third-party tokens don't use the reserved `kick/` prefix |
25
23
 
26
- ```ts
27
- import { runLint, formatViolations } from '@forinda/kickjs-lint'
28
-
29
- const result = await runLint({ cwd: process.cwd(), firstParty: false })
30
- if (result.violations.length > 0) console.error(formatViolations(result.violations))
31
- ```
24
+ Disable one on a line with `// kick-lint-disable <rule>`. `runLint()` and `formatViolations()` run it from code.
32
25
 
33
- ## Rules
26
+ ## Documentation
34
27
 
35
- | Rule | Default | Description |
36
- | ----------------------- | ------- | ----------------------------------------------------------------------------- |
37
- | `di-token-symbol` | `error` | DI tokens must use `createToken<T>()` instead of `Symbol(...)` in token files |
38
- | `token-kick-prefix` | `error` | First-party tokens must start with the reserved `kick/` prefix |
39
- | `token-reserved-prefix` | `warn` | Third-party tokens must not squat the reserved `kick/` prefix |
40
-
41
- Inline-disable a rule on a single line:
42
-
43
- ```ts
44
- export const FOO = Symbol('Legacy') // kick-lint-disable di-token-symbol
45
- ```
28
+ [DI tokens](https://kickjs.app/guide/dependency-injection#tokens)
46
29
 
47
- ## Why
30
+ ## License
48
31
 
49
- See [architecture.md §22](https://github.com/forinda/kick-js/blob/main/architecture.md#22-di-token-convention--symbol-to-string-migration) for the convention rationale. The convention landed in the v3 → v4 migration and has been stable since; refer to the [DI Tokens guide](https://kickjs.app/guide/dependency-injection.html#tokens) for the current shape.
32
+ MIT
package/dist/index.d.mts CHANGED
@@ -1,6 +1,5 @@
1
1
 
2
2
  import { LintContext, Rule, Violation, diTokenSymbol, rules, tokenKickPrefix, tokenReservedPrefix } from "./rules.mjs";
3
-
4
3
  //#region src/index.d.ts
5
4
  /**
6
5
  * Filenames that historically declare DI tokens. Lint runs only on
@@ -11,8 +10,8 @@ import { LintContext, Rule, Violation, diTokenSymbol, rules, tokenKickPrefix, to
11
10
  * regression where `TENANT_DB = Symbol(...)` slipped past the rule by
12
11
  * living in `database.ts` rather than `tokens.ts` / `constants.ts`.
13
12
  */
14
- declare const TOKEN_FILE_NAMES: Set<string>;
15
- interface LintOptions {
13
+ export declare const TOKEN_FILE_NAMES: Set<string>;
14
+ export interface LintOptions {
16
15
  /** Root directory to walk. Required. */
17
16
  cwd: string;
18
17
  /**
@@ -32,7 +31,7 @@ interface LintOptions {
32
31
  /** Filenames to lint (defaults to {@link TOKEN_FILE_NAMES}). */
33
32
  filenames?: ReadonlySet<string>;
34
33
  }
35
- interface LintResult {
34
+ export interface LintResult {
36
35
  violations: Violation[];
37
36
  filesScanned: number;
38
37
  }
@@ -41,13 +40,13 @@ interface LintResult {
41
40
  * each token-bearing file, and return the aggregated violations + the
42
41
  * number of files actually checked.
43
42
  */
44
- declare function runLint(opts: LintOptions): Promise<LintResult>;
43
+ export declare function runLint(opts: LintOptions): Promise<LintResult>;
45
44
  /**
46
45
  * Format a violation list for terminal output. One block per violation,
47
46
  * trailing summary line. Returns `''` when there are no violations so
48
47
  * callers can branch on truthiness.
49
48
  */
50
- declare function formatViolations(violations: readonly Violation[]): string;
49
+ export declare function formatViolations(violations: readonly Violation[]): string;
51
50
  //#endregion
52
- export { type LintContext, LintOptions, LintResult, type Rule, TOKEN_FILE_NAMES, type Violation, rules as defaultRules, diTokenSymbol, formatViolations, runLint, tokenKickPrefix, tokenReservedPrefix };
51
+ export { type LintContext, type Rule, type Violation, rules as defaultRules, diTokenSymbol, tokenKickPrefix, tokenReservedPrefix };
53
52
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;;;;AA2EyB;AAGzB;;;;cAhDa,gBAAA,EAAgB,GAAA;AAAA,UA2BZ,WAAA;EAuBf;EArBA,GAAA;EAqBY;AAQd;;;;EAvBE,UAAA;EAuBgD;;;;;EAjBhD,KAAA;EAiBwD;EAfxD,KAAA,YAAiB,IAAA;EAeiD;EAblE,SAAA,GAAY,WAAW;AAAA;AAAA,UAGR,UAAA;EACf,UAAA,EAAY,SAAS;EACrB,YAAA;AAAA;;;;;;iBAQoB,OAAA,CAAQ,IAAA,EAAM,WAAA,GAAc,OAAA,CAAQ,UAAA;;;;;;iBAkD1C,gBAAA,CAAiB,UAAgC,WAAX,SAAS"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;;;;;;;;qBA8Ba,kBAAgB;iBA2BZ;;EAEf;;;;;;EAMA;;;;;;EAMA;;EAEA,iBAAiB;;EAEjB,YAAY;;iBAGG;EACf,YAAY;EACZ;;;;;;;wBAQoB,QAAQ,MAAM,cAAc,QAAQ;;;;;;wBAkD1C,iBAAiB,qBAAqB"}
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs-lint v5.2.4
2
+ * @forinda/kickjs-lint v5.2.6
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -8,6 +8,6 @@
8
8
  *
9
9
  * @license MIT
10
10
  */
11
- import{diTokenSymbol,rules,tokenKickPrefix,tokenReservedPrefix}from"./rules.mjs";import{readFile,readdir}from"node:fs/promises";import{join,relative}from"node:path";const TOKEN_FILE_NAMES=new Set([`types.ts`,`tokens.ts`,`constants.ts`,`service.ts`,`adapter.ts`,`database.ts`,`interfaces.ts`,`client.ts`]),SKIP_DIRS=new Set([`node_modules`,`dist`,`__tests__`,`.kickjs`,`public`,`.vitepress`,`.turbo`,`.wireit`,`coverage`]);async function runLint(opts){let cwd=opts.cwd,firstParty=opts.firstParty??!1,scope=opts.scope??[`packages`],ruleset=opts.rules??rules,filenames=opts.filenames??TOKEN_FILE_NAMES,violations=[],filesScanned=0;for(let root of scope)await walk(join(cwd,root),async file=>{let base=file.slice(file.lastIndexOf(`/`)+1);if(!filenames.has(base))return;let source=await readFile(file,`utf-8`),rel=relative(cwd,file);filesScanned++;for(let rule of ruleset)violations.push(...rule.check({source,file:rel,firstParty}))});return{violations,filesScanned}}async function walk(dir,visit){let entries;try{entries=await readdir(dir,{withFileTypes:!0})}catch{return}for(let entry of entries){let full=join(dir,entry.name);if(entry.isDirectory()){if(SKIP_DIRS.has(entry.name))continue;await walk(full,visit)}else entry.isFile()&&await visit(full)}}function formatViolations(violations){if(violations.length===0)return``;let lines=[];for(let v of violations){let tag=v.severity===`error`?`error`:`warn`;lines.push(` [${tag}] ${v.file}:${v.line} ${v.ruleId}`),lines.push(` ${v.message}`),v.suggestion&&lines.push(` -> ${v.suggestion}`),lines.push(``)}let errors=violations.filter(v=>v.severity===`error`).length,warnings=violations.length-errors;return lines.push(`${violations.length} violation(s): ${errors} error, ${warnings} warn`),lines.join(`
11
+ import{diTokenSymbol,rules,tokenKickPrefix,tokenReservedPrefix}from"./rules.mjs";import{readFile,readdir}from"node:fs/promises";import{join,relative}from"node:path";const TOKEN_FILE_NAMES=/* @__PURE__ */ new Set([`types.ts`,`tokens.ts`,`constants.ts`,`service.ts`,`adapter.ts`,`database.ts`,`interfaces.ts`,`client.ts`]),SKIP_DIRS=/* @__PURE__ */ new Set([`node_modules`,`dist`,`__tests__`,`.kickjs`,`public`,`.vitepress`,`.turbo`,`.wireit`,`coverage`]);async function runLint(opts){let cwd=opts.cwd,firstParty=opts.firstParty??!1,scope=opts.scope??[`packages`],ruleset=opts.rules??rules,filenames=opts.filenames??TOKEN_FILE_NAMES,violations=[],filesScanned=0;for(let root of scope)await walk(join(cwd,root),async file=>{let base=file.slice(file.lastIndexOf(`/`)+1);if(!filenames.has(base))return;let source=await readFile(file,`utf-8`),rel=relative(cwd,file);filesScanned++;for(let rule of ruleset)violations.push(...rule.check({source,file:rel,firstParty}))});return{violations,filesScanned}}async function walk(dir,visit){let entries;try{entries=await readdir(dir,{withFileTypes:!0})}catch{return}for(let entry of entries){let full=join(dir,entry.name);if(entry.isDirectory()){if(SKIP_DIRS.has(entry.name))continue;await walk(full,visit)}else entry.isFile()&&await visit(full)}}function formatViolations(violations){if(violations.length===0)return``;let lines=[];for(let v of violations){let tag=v.severity===`error`?`error`:`warn`;lines.push(` [${tag}] ${v.file}:${v.line} ${v.ruleId}`),lines.push(` ${v.message}`),v.suggestion&&lines.push(` -> ${v.suggestion}`),lines.push(``)}let errors=violations.filter(v=>v.severity===`error`).length,warnings=violations.length-errors;return lines.push(`${violations.length} violation(s): ${errors} error, ${warnings} warn`),lines.join(`
12
12
  `)}export{TOKEN_FILE_NAMES,rules as defaultRules,diTokenSymbol,formatViolations,runLint,tokenKickPrefix,tokenReservedPrefix};
13
13
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["defaultRules"],"sources":["../src/index.ts"],"sourcesContent":["/**\n * Programmatic entry point for `@forinda/kickjs-lint`.\n *\n * Walks a directory tree, applies every enabled rule from\n * {@link ./rules} to each file that matches the token-bearing filename\n * heuristic, and returns the aggregated violations.\n *\n * The CLI binary (`bin.mjs`) wraps this with a process-exit translation\n * layer; downstream tools (devtools, editor extensions) call this\n * directly.\n *\n * @module @forinda/kickjs-lint\n */\n\nimport { readdir, readFile } from 'node:fs/promises'\nimport { join, relative } from 'node:path'\nimport { rules as defaultRules, type Rule, type Violation } from './rules'\n\nexport type { LintContext, Rule, Violation } from './rules'\nexport { diTokenSymbol, tokenKickPrefix, tokenReservedPrefix, rules as defaultRules } from './rules'\n\n/**\n * Filenames that historically declare DI tokens. Lint runs only on\n * these to keep false positives out of unrelated files (e.g. a class\n * named `Symbol` in a math library).\n *\n * `database.ts`, `interfaces.ts`, `client.ts` were added after a\n * regression where `TENANT_DB = Symbol(...)` slipped past the rule by\n * living in `database.ts` rather than `tokens.ts` / `constants.ts`.\n */\nexport const TOKEN_FILE_NAMES = new Set([\n 'types.ts',\n 'tokens.ts',\n 'constants.ts',\n 'service.ts',\n 'adapter.ts',\n 'database.ts',\n 'interfaces.ts',\n 'client.ts',\n])\n\n/** Directories the walker skips wholesale. */\nconst SKIP_DIRS = new Set([\n 'node_modules',\n 'dist',\n '__tests__',\n '.kickjs',\n // Built static assets (e.g. devtools SPA bundle) live under `public/`\n // and ship as-is to npm — no token literals to lint there.\n 'public',\n // VitePress / Vite build outputs that adopters sometimes commit.\n '.vitepress',\n '.turbo',\n '.wireit',\n 'coverage',\n])\n\nexport interface LintOptions {\n /** Root directory to walk. Required. */\n cwd: string\n /**\n * Whether the project being linted is a first-party `@forinda/kickjs-*`\n * package (enables `token-kick-prefix`, disables `token-reserved-prefix`).\n * Defaults to `false` so adopter projects use the friendlier ruleset.\n */\n firstParty?: boolean\n /**\n * Only lint files inside these subdirectories (relative to `cwd`).\n * Defaults to `['packages']` for first-party use; pass `['src']` or\n * `['.']` for adopter projects.\n */\n scope?: readonly string[]\n /** Override the rule set. Defaults to every rule shipped by the package. */\n rules?: readonly Rule[]\n /** Filenames to lint (defaults to {@link TOKEN_FILE_NAMES}). */\n filenames?: ReadonlySet<string>\n}\n\nexport interface LintResult {\n violations: Violation[]\n filesScanned: number\n}\n\n/**\n * Walk `cwd` (constrained to `scope` directories), apply every rule to\n * each token-bearing file, and return the aggregated violations + the\n * number of files actually checked.\n */\nexport async function runLint(opts: LintOptions): Promise<LintResult> {\n const cwd = opts.cwd\n const firstParty = opts.firstParty ?? false\n const scope = opts.scope ?? ['packages']\n const ruleset = opts.rules ?? defaultRules\n const filenames = opts.filenames ?? TOKEN_FILE_NAMES\n\n const violations: Violation[] = []\n let filesScanned = 0\n\n for (const root of scope) {\n const start = join(cwd, root)\n await walk(start, async (file) => {\n const base = file.slice(file.lastIndexOf('/') + 1)\n if (!filenames.has(base)) return\n const source = await readFile(file, 'utf-8')\n const rel = relative(cwd, file)\n filesScanned++\n for (const rule of ruleset) {\n violations.push(...rule.check({ source, file: rel, firstParty }))\n }\n })\n }\n\n return { violations, filesScanned }\n}\n\nasync function walk(dir: string, visit: (file: string) => Promise<void>): Promise<void> {\n let entries\n try {\n entries = await readdir(dir, { withFileTypes: true })\n } catch {\n return\n }\n for (const entry of entries) {\n const full = join(dir, entry.name)\n if (entry.isDirectory()) {\n if (SKIP_DIRS.has(entry.name)) continue\n await walk(full, visit)\n } else if (entry.isFile()) {\n await visit(full)\n }\n }\n}\n\n/**\n * Format a violation list for terminal output. One block per violation,\n * trailing summary line. Returns `''` when there are no violations so\n * callers can branch on truthiness.\n */\nexport function formatViolations(violations: readonly Violation[]): string {\n if (violations.length === 0) return ''\n const lines: string[] = []\n for (const v of violations) {\n const tag = v.severity === 'error' ? 'error' : 'warn'\n lines.push(` [${tag}] ${v.file}:${v.line} ${v.ruleId}`)\n lines.push(` ${v.message}`)\n if (v.suggestion) lines.push(` -> ${v.suggestion}`)\n lines.push('')\n }\n const errors = violations.filter((v) => v.severity === 'error').length\n const warnings = violations.length - errors\n lines.push(`${violations.length} violation(s): ${errors} error, ${warnings} warn`)\n return lines.join('\\n')\n}\n"],"mappings":";;;;;;;;;;qKA8BA,MAAa,iBAAmB,IAAI,IAAI,CACtC,WACA,YACA,eACA,aACA,aACA,cACA,gBACA,WACF,CAAC,EAGK,UAAY,IAAI,IAAI,CACxB,eACA,OACA,YACA,UAGA,SAEA,aACA,SACA,UACA,UACF,CAAC,EAiCD,eAAsB,QAAQ,KAAwC,CACpE,IAAM,IAAM,KAAK,IACX,WAAa,KAAK,YAAc,GAChC,MAAQ,KAAK,OAAS,CAAC,UAAU,EACjC,QAAU,KAAK,OAASA,MACxB,UAAY,KAAK,WAAa,iBAE9B,WAA0B,CAAC,EAC7B,aAAe,EAEnB,IAAK,IAAM,QAAQ,MAEjB,MAAM,KADQ,KAAK,IAAK,IACT,EAAG,KAAO,OAAS,CAChC,IAAM,KAAO,KAAK,MAAM,KAAK,YAAY,GAAG,EAAI,CAAC,EACjD,GAAI,CAAC,UAAU,IAAI,IAAI,EAAG,OAC1B,IAAM,OAAS,MAAM,SAAS,KAAM,OAAO,EACrC,IAAM,SAAS,IAAK,IAAI,EAC9B,eACA,IAAK,IAAM,QAAQ,QACjB,WAAW,KAAK,GAAG,KAAK,MAAM,CAAE,OAAQ,KAAM,IAAK,UAAW,CAAC,CAAC,CAEpE,CAAC,EAGH,MAAO,CAAE,WAAY,YAAa,CACpC,CAEA,eAAe,KAAK,IAAa,MAAuD,CACtF,IAAI,QACJ,GAAI,CACF,QAAU,MAAM,QAAQ,IAAK,CAAE,cAAe,EAAK,CAAC,CACtD,MAAQ,CACN,MACF,CACA,IAAK,IAAM,SAAS,QAAS,CAC3B,IAAM,KAAO,KAAK,IAAK,MAAM,IAAI,EACjC,GAAI,MAAM,YAAY,EAAG,CACvB,GAAI,UAAU,IAAI,MAAM,IAAI,EAAG,SAC/B,MAAM,KAAK,KAAM,KAAK,CACxB,MAAW,MAAM,OAAO,GACtB,MAAM,MAAM,IAAI,CAEpB,CACF,CAOA,SAAgB,iBAAiB,WAA0C,CACzE,GAAI,WAAW,SAAW,EAAG,MAAO,GACpC,IAAM,MAAkB,CAAC,EACzB,IAAK,IAAM,KAAK,WAAY,CAC1B,IAAM,IAAM,EAAE,WAAa,QAAU,QAAU,OAC/C,MAAM,KAAK,MAAM,IAAI,IAAI,EAAE,KAAK,GAAG,EAAE,KAAK,IAAI,EAAE,QAAQ,EACxD,MAAM,KAAK,OAAO,EAAE,SAAS,EACzB,EAAE,YAAY,MAAM,KAAK,UAAU,EAAE,YAAY,EACrD,MAAM,KAAK,EAAE,CACf,CACA,IAAM,OAAS,WAAW,OAAQ,GAAM,EAAE,WAAa,OAAO,CAAC,CAAC,OAC1D,SAAW,WAAW,OAAS,OAErC,OADA,MAAM,KAAK,GAAG,WAAW,OAAO,iBAAiB,OAAO,UAAU,SAAS,MAAM,EAC1E,MAAM,KAAK;CAAI,CACxB"}
1
+ {"version":3,"file":"index.mjs","names":["defaultRules"],"sources":["../src/index.ts"],"sourcesContent":["/**\n * Programmatic entry point for `@forinda/kickjs-lint`.\n *\n * Walks a directory tree, applies every enabled rule from\n * {@link ./rules} to each file that matches the token-bearing filename\n * heuristic, and returns the aggregated violations.\n *\n * The CLI binary (`bin.mjs`) wraps this with a process-exit translation\n * layer; downstream tools (devtools, editor extensions) call this\n * directly.\n *\n * @module @forinda/kickjs-lint\n */\n\nimport { readdir, readFile } from 'node:fs/promises'\nimport { join, relative } from 'node:path'\nimport { rules as defaultRules, type Rule, type Violation } from './rules'\n\nexport type { LintContext, Rule, Violation } from './rules'\nexport { diTokenSymbol, tokenKickPrefix, tokenReservedPrefix, rules as defaultRules } from './rules'\n\n/**\n * Filenames that historically declare DI tokens. Lint runs only on\n * these to keep false positives out of unrelated files (e.g. a class\n * named `Symbol` in a math library).\n *\n * `database.ts`, `interfaces.ts`, `client.ts` were added after a\n * regression where `TENANT_DB = Symbol(...)` slipped past the rule by\n * living in `database.ts` rather than `tokens.ts` / `constants.ts`.\n */\nexport const TOKEN_FILE_NAMES = new Set([\n 'types.ts',\n 'tokens.ts',\n 'constants.ts',\n 'service.ts',\n 'adapter.ts',\n 'database.ts',\n 'interfaces.ts',\n 'client.ts',\n])\n\n/** Directories the walker skips wholesale. */\nconst SKIP_DIRS = new Set([\n 'node_modules',\n 'dist',\n '__tests__',\n '.kickjs',\n // Built static assets (e.g. devtools SPA bundle) live under `public/`\n // and ship as-is to npm — no token literals to lint there.\n 'public',\n // VitePress / Vite build outputs that adopters sometimes commit.\n '.vitepress',\n '.turbo',\n '.wireit',\n 'coverage',\n])\n\nexport interface LintOptions {\n /** Root directory to walk. Required. */\n cwd: string\n /**\n * Whether the project being linted is a first-party `@forinda/kickjs-*`\n * package (enables `token-kick-prefix`, disables `token-reserved-prefix`).\n * Defaults to `false` so adopter projects use the friendlier ruleset.\n */\n firstParty?: boolean\n /**\n * Only lint files inside these subdirectories (relative to `cwd`).\n * Defaults to `['packages']` for first-party use; pass `['src']` or\n * `['.']` for adopter projects.\n */\n scope?: readonly string[]\n /** Override the rule set. Defaults to every rule shipped by the package. */\n rules?: readonly Rule[]\n /** Filenames to lint (defaults to {@link TOKEN_FILE_NAMES}). */\n filenames?: ReadonlySet<string>\n}\n\nexport interface LintResult {\n violations: Violation[]\n filesScanned: number\n}\n\n/**\n * Walk `cwd` (constrained to `scope` directories), apply every rule to\n * each token-bearing file, and return the aggregated violations + the\n * number of files actually checked.\n */\nexport async function runLint(opts: LintOptions): Promise<LintResult> {\n const cwd = opts.cwd\n const firstParty = opts.firstParty ?? false\n const scope = opts.scope ?? ['packages']\n const ruleset = opts.rules ?? defaultRules\n const filenames = opts.filenames ?? TOKEN_FILE_NAMES\n\n const violations: Violation[] = []\n let filesScanned = 0\n\n for (const root of scope) {\n const start = join(cwd, root)\n await walk(start, async (file) => {\n const base = file.slice(file.lastIndexOf('/') + 1)\n if (!filenames.has(base)) return\n const source = await readFile(file, 'utf-8')\n const rel = relative(cwd, file)\n filesScanned++\n for (const rule of ruleset) {\n violations.push(...rule.check({ source, file: rel, firstParty }))\n }\n })\n }\n\n return { violations, filesScanned }\n}\n\nasync function walk(dir: string, visit: (file: string) => Promise<void>): Promise<void> {\n let entries\n try {\n entries = await readdir(dir, { withFileTypes: true })\n } catch {\n return\n }\n for (const entry of entries) {\n const full = join(dir, entry.name)\n if (entry.isDirectory()) {\n if (SKIP_DIRS.has(entry.name)) continue\n await walk(full, visit)\n } else if (entry.isFile()) {\n await visit(full)\n }\n }\n}\n\n/**\n * Format a violation list for terminal output. One block per violation,\n * trailing summary line. Returns `''` when there are no violations so\n * callers can branch on truthiness.\n */\nexport function formatViolations(violations: readonly Violation[]): string {\n if (violations.length === 0) return ''\n const lines: string[] = []\n for (const v of violations) {\n const tag = v.severity === 'error' ? 'error' : 'warn'\n lines.push(` [${tag}] ${v.file}:${v.line} ${v.ruleId}`)\n lines.push(` ${v.message}`)\n if (v.suggestion) lines.push(` -> ${v.suggestion}`)\n lines.push('')\n }\n const errors = violations.filter((v) => v.severity === 'error').length\n const warnings = violations.length - errors\n lines.push(`${violations.length} violation(s): ${errors} error, ${warnings} warn`)\n return lines.join('\\n')\n}\n"],"mappings":";;;;;;;;;;qKA8BA,MAAa,iCAAmB,IAAI,IAAI,CACtC,WACA,YACA,eACA,aACA,aACA,cACA,gBACA,WACF,CAAC,EAGK,0BAAY,IAAI,IAAI,CACxB,eACA,OACA,YACA,UAGA,SAEA,aACA,SACA,UACA,UACF,CAAC,EAiCD,eAAsB,QAAQ,KAAwC,CACpE,IAAM,IAAM,KAAK,IACX,WAAa,KAAK,YAAc,GAChC,MAAQ,KAAK,OAAS,CAAC,UAAU,EACjC,QAAU,KAAK,OAASA,MACxB,UAAY,KAAK,WAAa,iBAE9B,WAA0B,CAAC,EAC7B,aAAe,EAEnB,IAAK,IAAM,QAAQ,MAEjB,MAAM,KADQ,KAAK,IAAK,IACT,EAAG,KAAO,OAAS,CAChC,IAAM,KAAO,KAAK,MAAM,KAAK,YAAY,GAAG,EAAI,CAAC,EACjD,GAAI,CAAC,UAAU,IAAI,IAAI,EAAG,OAC1B,IAAM,OAAS,MAAM,SAAS,KAAM,OAAO,EACrC,IAAM,SAAS,IAAK,IAAI,EAC9B,eACA,IAAK,IAAM,QAAQ,QACjB,WAAW,KAAK,GAAG,KAAK,MAAM,CAAE,OAAQ,KAAM,IAAK,UAAW,CAAC,CAAC,CAEpE,CAAC,EAGH,MAAO,CAAE,WAAY,YAAa,CACpC,CAEA,eAAe,KAAK,IAAa,MAAuD,CACtF,IAAI,QACJ,GAAI,CACF,QAAU,MAAM,QAAQ,IAAK,CAAE,cAAe,EAAK,CAAC,CACtD,MAAQ,CACN,MACF,CACA,IAAK,IAAM,SAAS,QAAS,CAC3B,IAAM,KAAO,KAAK,IAAK,MAAM,IAAI,EACjC,GAAI,MAAM,YAAY,EAAG,CACvB,GAAI,UAAU,IAAI,MAAM,IAAI,EAAG,SAC/B,MAAM,KAAK,KAAM,KAAK,CACxB,MAAW,MAAM,OAAO,GACtB,MAAM,MAAM,IAAI,CAEpB,CACF,CAOA,SAAgB,iBAAiB,WAA0C,CACzE,GAAI,WAAW,SAAW,EAAG,MAAO,GACpC,IAAM,MAAkB,CAAC,EACzB,IAAK,IAAM,KAAK,WAAY,CAC1B,IAAM,IAAM,EAAE,WAAa,QAAU,QAAU,OAC/C,MAAM,KAAK,MAAM,IAAI,IAAI,EAAE,KAAK,GAAG,EAAE,KAAK,IAAI,EAAE,QAAQ,EACxD,MAAM,KAAK,OAAO,EAAE,SAAS,EACzB,EAAE,YAAY,MAAM,KAAK,UAAU,EAAE,YAAY,EACrD,MAAM,KAAK,EAAE,CACf,CACA,IAAM,OAAS,WAAW,OAAQ,GAAM,EAAE,WAAa,OAAO,CAAC,CAAC,OAC1D,SAAW,WAAW,OAAS,OAErC,OADA,MAAM,KAAK,GAAG,WAAW,OAAO,iBAAiB,OAAO,UAAU,SAAS,MAAM,EAC1E,MAAM,KAAK;CAAI,CACxB"}
package/dist/rules.d.mts CHANGED
@@ -5,7 +5,7 @@
5
5
  * and returns the violations it found. Stateless and synchronous so the
6
6
  * runner can compose them freely (CLI, devtools, editor extension).
7
7
  */
8
- interface Violation {
8
+ export interface Violation {
9
9
  ruleId: string;
10
10
  severity: 'error' | 'warn';
11
11
  file: string;
@@ -14,7 +14,7 @@ interface Violation {
14
14
  message: string;
15
15
  suggestion?: string;
16
16
  }
17
- interface LintContext {
17
+ export interface LintContext {
18
18
  source: string;
19
19
  file: string;
20
20
  /**
@@ -24,7 +24,7 @@ interface LintContext {
24
24
  */
25
25
  firstParty: boolean;
26
26
  }
27
- interface Rule {
27
+ export interface Rule {
28
28
  id: string;
29
29
  description: string;
30
30
  defaultSeverity: 'error' | 'warn';
@@ -39,7 +39,7 @@ interface Rule {
39
39
  * Inline-disable: append `// kick-lint-disable di-token-symbol` to the
40
40
  * declaration line.
41
41
  */
42
- declare const diTokenSymbol: Rule;
42
+ export declare const diTokenSymbol: Rule;
43
43
  /**
44
44
  * For first-party files, every `createToken('literal')` literal must
45
45
  * start with the reserved `kick/` prefix. Third-party adopter code is
@@ -49,16 +49,15 @@ declare const diTokenSymbol: Rule;
49
49
  * `kickjs/` (the dotted convention pre-dates section 22 and gets
50
50
  * migrated alongside the symbol-to-string work).
51
51
  */
52
- declare const tokenKickPrefix: Rule;
52
+ export declare const tokenKickPrefix: Rule;
53
53
  /**
54
54
  * Third-party adopters must NOT squat the reserved `kick/` prefix --
55
55
  * mirror image of {@link tokenKickPrefix}. Runs only on non-first-party
56
56
  * code and warns rather than errors so the rule is informative without
57
57
  * blocking adopter installs.
58
58
  */
59
- declare const tokenReservedPrefix: Rule;
59
+ export declare const tokenReservedPrefix: Rule;
60
60
  /** All rules shipped by this package, in run order. */
61
- declare const rules: readonly Rule[];
61
+ export declare const rules: readonly Rule[];
62
62
  //#endregion
63
- export { LintContext, Rule, Violation, diTokenSymbol, rules, tokenKickPrefix, tokenReservedPrefix };
64
63
  //# sourceMappingURL=rules.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"rules.d.mts","names":[],"sources":["../src/rules.ts"],"mappings":";;;AAMA;;;;UAAiB,SAAA;EACf,MAAA;EACA,QAAA;EACA,IAAA;EAGA;EADA,IAAA;EACA,OAAA;EACA,UAAA;AAAA;AAAA,UAGe,WAAA;EACf,MAAA;EACA,IAAA;EADA;;;;AAOU;EAAV,UAAA;AAAA;AAAA,UAGe,IAAA;EACf,EAAA;EACA,WAAA;EACA,eAAA;EACA,KAAA,CAAM,GAAA,EAAK,WAAA,GAAc,SAAS;AAAA;;;;;AAAA;AAmBpC;;;;cAAa,aAAA,EAAe,IAwB3B;AAWD;;;;AAwBC;AAQD;;;;AAhCA,cAAa,eAAA,EAAiB,IAwB7B;AAkCD;;;;AAAiC;;AAAjC,cA1Ba,mBAAA,EAAqB,IAuBjC;;cAGY,KAAA,WAAgB,IAAI"}
1
+ {"version":3,"file":"rules.d.mts","names":[],"sources":["../src/rules.ts"],"mappings":";;;;;;;iBAMiB;EACf;EACA;EACA;;EAEA;EACA;EACA;;iBAGe;EACf;EACA;;;;;;EAMA;;iBAGe;EACf;EACA;EACA;EACA,MAAM,KAAK,cAAc;;;;;;;;;;;qBAsBd,eAAe;;;;;;;;;;qBAmCf,iBAAiB;;;;;;;qBAgCjB,qBAAqB;;qBA0BrB,gBAAgB"}
package/dist/rules.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs-lint v5.2.4
2
+ * @forinda/kickjs-lint v5.2.6
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -8,7 +8,7 @@
8
8
  *
9
9
  * @license MIT
10
10
  */
11
- const SYMBOL_DECL_REGEX=/\bexport\s+const\s+([A-Z_][A-Z0-9_]*)\s*=\s*Symbol\s*\(/g,CREATE_TOKEN_LITERAL_REGEX=/createToken\s*(?:<[^>]*>)?\s*\(\s*['"`]([^'"`]+)['"`]/g;function lineOf(source,index){return source.slice(0,index).split(`
11
+ const SYMBOL_DECL_REGEX=/\bexport\s+const\s+([A-Z_][A-Z0-9_]*)\s*=\s*Symbol\s*\(/g,CREATE_TOKEN_LITERAL_REGEX=/(?:createToken|(?<![\w$.])ctn)\s*(?:<[^>]*>)?\s*\(\s*['"`]([^'"`]+)['"`]/g;function lineOf(source,index){return source.slice(0,index).split(`
12
12
  `).length}const diTokenSymbol={id:`di-token-symbol`,description:`DI tokens must use createToken<T>() instead of Symbol(...)`,defaultSeverity:`error`,check({source,file}){let out=[],lines=source.split(`
13
13
  `);for(let i=0;i<lines.length;i++){let line=lines[i];if(line.includes(`// kick-lint-disable di-token-symbol`))continue;SYMBOL_DECL_REGEX.lastIndex=0;let match=SYMBOL_DECL_REGEX.exec(line);match&&out.push({ruleId:diTokenSymbol.id,severity:diTokenSymbol.defaultSeverity,file,line:i+1,message:`\`${match[1]} = Symbol(...)\` declares a DI token via Symbol`,suggestion:"Use `createToken<T>('kick/<area>/<key>')` -- see architecture.md section 22"})}return out}},tokenKickPrefix={id:`token-kick-prefix`,description:"First-party DI tokens must start with the reserved `kick/` prefix",defaultSeverity:`error`,check({source,file,firstParty}){if(!firstParty)return[];let out=[];CREATE_TOKEN_LITERAL_REGEX.lastIndex=0;let match;for(;(match=CREATE_TOKEN_LITERAL_REGEX.exec(source))!==null;){let literal=match[1];literal.startsWith(`kick/`)||literal.startsWith(`kickjs.`)||literal.startsWith(`kickjs/`)||out.push({ruleId:tokenKickPrefix.id,severity:tokenKickPrefix.defaultSeverity,file,line:lineOf(source,match.index),message:`\`createToken('${literal}')\` is missing the reserved \`kick/\` prefix`,suggestion:`Rename to \`'kick/${literal}'\` -- first-party tokens own the \`kick/\` namespace`})}return out}},tokenReservedPrefix={id:`token-reserved-prefix`,description:"Third-party tokens must not start with the reserved `kick/` prefix",defaultSeverity:`warn`,check({source,file,firstParty}){if(firstParty)return[];let out=[];CREATE_TOKEN_LITERAL_REGEX.lastIndex=0;let match;for(;(match=CREATE_TOKEN_LITERAL_REGEX.exec(source))!==null;){let literal=match[1];literal.startsWith(`kick/`)&&out.push({ruleId:tokenReservedPrefix.id,severity:tokenReservedPrefix.defaultSeverity,file,line:lineOf(source,match.index),message:`\`createToken('${literal}')\` squats the reserved \`kick/\` prefix`,suggestion:"Pick an org-scoped prefix instead (e.g. `'mycorp/...'`)"})}return out}},rules=[diTokenSymbol,tokenKickPrefix,tokenReservedPrefix];export{diTokenSymbol,rules,tokenKickPrefix,tokenReservedPrefix};
14
14
  //# sourceMappingURL=rules.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"rules.mjs","names":[],"sources":["../src/rules.ts"],"sourcesContent":["/**\n * Pure rule definitions. Each rule takes a source file's content + path\n * and returns the violations it found. Stateless and synchronous so the\n * runner can compose them freely (CLI, devtools, editor extension).\n */\n\nexport interface Violation {\n ruleId: string\n severity: 'error' | 'warn'\n file: string\n /** 1-based line number of the offending construct. */\n line: number\n message: string\n suggestion?: string\n}\n\nexport interface LintContext {\n source: string\n file: string\n /**\n * Whether the file lives inside `packages/<name>/src/` of the\n * `@forinda/kickjs-*` monorepo. First-party files are held to the\n * stricter `kick/` prefix; third-party adopter code is exempt.\n */\n firstParty: boolean\n}\n\nexport interface Rule {\n id: string\n description: string\n defaultSeverity: 'error' | 'warn'\n check(ctx: LintContext): Violation[]\n}\n\nconst SYMBOL_DECL_REGEX = /\\bexport\\s+const\\s+([A-Z_][A-Z0-9_]*)\\s*=\\s*Symbol\\s*\\(/g\nconst CREATE_TOKEN_LITERAL_REGEX = /createToken\\s*(?:<[^>]*>)?\\s*\\(\\s*['\"`]([^'\"`]+)['\"`]/g\n\nfunction lineOf(source: string, index: number): number {\n return source.slice(0, index).split('\\n').length\n}\n\n/**\n * Forbid `export const X = Symbol('...')` declarations in token-bearing\n * files. The §22 v4 migration replaced first-party DI tokens with\n * `createToken<T>()`; this rule blocks regression in framework code and\n * encourages adopters to follow the same pattern.\n *\n * Inline-disable: append `// kick-lint-disable di-token-symbol` to the\n * declaration line.\n */\nexport const diTokenSymbol: Rule = {\n id: 'di-token-symbol',\n description: 'DI tokens must use createToken<T>() instead of Symbol(...)',\n defaultSeverity: 'error',\n check({ source, file }) {\n const out: Violation[] = []\n const lines = source.split('\\n')\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i]\n if (line.includes('// kick-lint-disable di-token-symbol')) continue\n SYMBOL_DECL_REGEX.lastIndex = 0\n const match = SYMBOL_DECL_REGEX.exec(line)\n if (!match) continue\n out.push({\n ruleId: diTokenSymbol.id,\n severity: diTokenSymbol.defaultSeverity,\n file,\n line: i + 1,\n message: `\\`${match[1]} = Symbol(...)\\` declares a DI token via Symbol`,\n suggestion: `Use \\`createToken<T>('kick/<area>/<key>')\\` -- see architecture.md section 22`,\n })\n }\n return out\n },\n}\n\n/**\n * For first-party files, every `createToken('literal')` literal must\n * start with the reserved `kick/` prefix. Third-party adopter code is\n * exempt -- they use their own scope (`mycorp/`, `acme/`, etc.).\n *\n * Allowed back-compat exception: literals starting with `kickjs.` or\n * `kickjs/` (the dotted convention pre-dates section 22 and gets\n * migrated alongside the symbol-to-string work).\n */\nexport const tokenKickPrefix: Rule = {\n id: 'token-kick-prefix',\n description: 'First-party DI tokens must start with the reserved `kick/` prefix',\n defaultSeverity: 'error',\n check({ source, file, firstParty }) {\n if (!firstParty) return []\n const out: Violation[] = []\n CREATE_TOKEN_LITERAL_REGEX.lastIndex = 0\n let match: RegExpExecArray | null\n while ((match = CREATE_TOKEN_LITERAL_REGEX.exec(source)) !== null) {\n const literal = match[1]\n if (literal.startsWith('kick/')) continue\n if (literal.startsWith('kickjs.') || literal.startsWith('kickjs/')) continue\n out.push({\n ruleId: tokenKickPrefix.id,\n severity: tokenKickPrefix.defaultSeverity,\n file,\n line: lineOf(source, match.index),\n message: `\\`createToken('${literal}')\\` is missing the reserved \\`kick/\\` prefix`,\n suggestion: `Rename to \\`'kick/${literal}'\\` -- first-party tokens own the \\`kick/\\` namespace`,\n })\n }\n return out\n },\n}\n\n/**\n * Third-party adopters must NOT squat the reserved `kick/` prefix --\n * mirror image of {@link tokenKickPrefix}. Runs only on non-first-party\n * code and warns rather than errors so the rule is informative without\n * blocking adopter installs.\n */\nexport const tokenReservedPrefix: Rule = {\n id: 'token-reserved-prefix',\n description: 'Third-party tokens must not start with the reserved `kick/` prefix',\n defaultSeverity: 'warn',\n check({ source, file, firstParty }) {\n if (firstParty) return []\n const out: Violation[] = []\n CREATE_TOKEN_LITERAL_REGEX.lastIndex = 0\n let match: RegExpExecArray | null\n while ((match = CREATE_TOKEN_LITERAL_REGEX.exec(source)) !== null) {\n const literal = match[1]\n if (!literal.startsWith('kick/')) continue\n out.push({\n ruleId: tokenReservedPrefix.id,\n severity: tokenReservedPrefix.defaultSeverity,\n file,\n line: lineOf(source, match.index),\n message: `\\`createToken('${literal}')\\` squats the reserved \\`kick/\\` prefix`,\n suggestion: `Pick an org-scoped prefix instead (e.g. \\`'mycorp/...'\\`)`,\n })\n }\n return out\n },\n}\n\n/** All rules shipped by this package, in run order. */\nexport const rules: readonly Rule[] = [diTokenSymbol, tokenKickPrefix, tokenReservedPrefix]\n"],"mappings":";;;;;;;;;;AAkCA,MAAM,kBAAoB,2DACpB,2BAA6B,yDAEnC,SAAS,OAAO,OAAgB,MAAuB,CACrD,OAAO,OAAO,MAAM,EAAG,KAAK,CAAC,CAAC,MAAM;CAAI,CAAC,CAAC,MAC5C,CAWA,MAAa,cAAsB,CACjC,GAAI,kBACJ,YAAa,6DACb,gBAAiB,QACjB,MAAM,CAAE,OAAQ,MAAQ,CACtB,IAAM,IAAmB,CAAC,EACpB,MAAQ,OAAO,MAAM;CAAI,EAC/B,IAAK,IAAI,EAAI,EAAG,EAAI,MAAM,OAAQ,IAAK,CACrC,IAAM,KAAO,MAAM,GACnB,GAAI,KAAK,SAAS,sCAAsC,EAAG,SAC3D,kBAAkB,UAAY,EAC9B,IAAM,MAAQ,kBAAkB,KAAK,IAAI,EACpC,OACL,IAAI,KAAK,CACP,OAAQ,cAAc,GACtB,SAAU,cAAc,gBACxB,KACA,KAAM,EAAI,EACV,QAAS,KAAK,MAAM,GAAG,iDACvB,WAAY,6EACd,CAAC,CACH,CACA,OAAO,GACT,CACF,EAWa,gBAAwB,CACnC,GAAI,oBACJ,YAAa,oEACb,gBAAiB,QACjB,MAAM,CAAE,OAAQ,KAAM,YAAc,CAClC,GAAI,CAAC,WAAY,MAAO,CAAC,EACzB,IAAM,IAAmB,CAAC,EAC1B,2BAA2B,UAAY,EACvC,IAAI,MACJ,MAAQ,MAAQ,2BAA2B,KAAK,MAAM,KAAO,MAAM,CACjE,IAAM,QAAU,MAAM,GAClB,QAAQ,WAAW,OAAO,GAC1B,QAAQ,WAAW,SAAS,GAAK,QAAQ,WAAW,SAAS,GACjE,IAAI,KAAK,CACP,OAAQ,gBAAgB,GACxB,SAAU,gBAAgB,gBAC1B,KACA,KAAM,OAAO,OAAQ,MAAM,KAAK,EAChC,QAAS,kBAAkB,QAAQ,+CACnC,WAAY,qBAAqB,QAAQ,sDAC3C,CAAC,CACH,CACA,OAAO,GACT,CACF,EAQa,oBAA4B,CACvC,GAAI,wBACJ,YAAa,qEACb,gBAAiB,OACjB,MAAM,CAAE,OAAQ,KAAM,YAAc,CAClC,GAAI,WAAY,MAAO,CAAC,EACxB,IAAM,IAAmB,CAAC,EAC1B,2BAA2B,UAAY,EACvC,IAAI,MACJ,MAAQ,MAAQ,2BAA2B,KAAK,MAAM,KAAO,MAAM,CACjE,IAAM,QAAU,MAAM,GACjB,QAAQ,WAAW,OAAO,GAC/B,IAAI,KAAK,CACP,OAAQ,oBAAoB,GAC5B,SAAU,oBAAoB,gBAC9B,KACA,KAAM,OAAO,OAAQ,MAAM,KAAK,EAChC,QAAS,kBAAkB,QAAQ,2CACnC,WAAY,yDACd,CAAC,CACH,CACA,OAAO,GACT,CACF,EAGa,MAAyB,CAAC,cAAe,gBAAiB,mBAAmB"}
1
+ {"version":3,"file":"rules.mjs","names":[],"sources":["../src/rules.ts"],"sourcesContent":["/**\n * Pure rule definitions. Each rule takes a source file's content + path\n * and returns the violations it found. Stateless and synchronous so the\n * runner can compose them freely (CLI, devtools, editor extension).\n */\n\nexport interface Violation {\n ruleId: string\n severity: 'error' | 'warn'\n file: string\n /** 1-based line number of the offending construct. */\n line: number\n message: string\n suggestion?: string\n}\n\nexport interface LintContext {\n source: string\n file: string\n /**\n * Whether the file lives inside `packages/<name>/src/` of the\n * `@forinda/kickjs-*` monorepo. First-party files are held to the\n * stricter `kick/` prefix; third-party adopter code is exempt.\n */\n firstParty: boolean\n}\n\nexport interface Rule {\n id: string\n description: string\n defaultSeverity: 'error' | 'warn'\n check(ctx: LintContext): Violation[]\n}\n\nconst SYMBOL_DECL_REGEX = /\\bexport\\s+const\\s+([A-Z_][A-Z0-9_]*)\\s*=\\s*Symbol\\s*\\(/g\n// `ctn` is createToken's short alias, held to the same conventions — the\n// function only: not a longer name ending in it, nor a method (`obj.ctn(`).\nconst CREATE_TOKEN_LITERAL_REGEX =\n /(?:createToken|(?<![\\w$.])ctn)\\s*(?:<[^>]*>)?\\s*\\(\\s*['\"`]([^'\"`]+)['\"`]/g\n\nfunction lineOf(source: string, index: number): number {\n return source.slice(0, index).split('\\n').length\n}\n\n/**\n * Forbid `export const X = Symbol('...')` declarations in token-bearing\n * files. The §22 v4 migration replaced first-party DI tokens with\n * `createToken<T>()`; this rule blocks regression in framework code and\n * encourages adopters to follow the same pattern.\n *\n * Inline-disable: append `// kick-lint-disable di-token-symbol` to the\n * declaration line.\n */\nexport const diTokenSymbol: Rule = {\n id: 'di-token-symbol',\n description: 'DI tokens must use createToken<T>() instead of Symbol(...)',\n defaultSeverity: 'error',\n check({ source, file }) {\n const out: Violation[] = []\n const lines = source.split('\\n')\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i]\n if (line.includes('// kick-lint-disable di-token-symbol')) continue\n SYMBOL_DECL_REGEX.lastIndex = 0\n const match = SYMBOL_DECL_REGEX.exec(line)\n if (!match) continue\n out.push({\n ruleId: diTokenSymbol.id,\n severity: diTokenSymbol.defaultSeverity,\n file,\n line: i + 1,\n message: `\\`${match[1]} = Symbol(...)\\` declares a DI token via Symbol`,\n suggestion: `Use \\`createToken<T>('kick/<area>/<key>')\\` -- see architecture.md section 22`,\n })\n }\n return out\n },\n}\n\n/**\n * For first-party files, every `createToken('literal')` literal must\n * start with the reserved `kick/` prefix. Third-party adopter code is\n * exempt -- they use their own scope (`mycorp/`, `acme/`, etc.).\n *\n * Allowed back-compat exception: literals starting with `kickjs.` or\n * `kickjs/` (the dotted convention pre-dates section 22 and gets\n * migrated alongside the symbol-to-string work).\n */\nexport const tokenKickPrefix: Rule = {\n id: 'token-kick-prefix',\n description: 'First-party DI tokens must start with the reserved `kick/` prefix',\n defaultSeverity: 'error',\n check({ source, file, firstParty }) {\n if (!firstParty) return []\n const out: Violation[] = []\n CREATE_TOKEN_LITERAL_REGEX.lastIndex = 0\n let match: RegExpExecArray | null\n while ((match = CREATE_TOKEN_LITERAL_REGEX.exec(source)) !== null) {\n const literal = match[1]\n if (literal.startsWith('kick/')) continue\n if (literal.startsWith('kickjs.') || literal.startsWith('kickjs/')) continue\n out.push({\n ruleId: tokenKickPrefix.id,\n severity: tokenKickPrefix.defaultSeverity,\n file,\n line: lineOf(source, match.index),\n message: `\\`createToken('${literal}')\\` is missing the reserved \\`kick/\\` prefix`,\n suggestion: `Rename to \\`'kick/${literal}'\\` -- first-party tokens own the \\`kick/\\` namespace`,\n })\n }\n return out\n },\n}\n\n/**\n * Third-party adopters must NOT squat the reserved `kick/` prefix --\n * mirror image of {@link tokenKickPrefix}. Runs only on non-first-party\n * code and warns rather than errors so the rule is informative without\n * blocking adopter installs.\n */\nexport const tokenReservedPrefix: Rule = {\n id: 'token-reserved-prefix',\n description: 'Third-party tokens must not start with the reserved `kick/` prefix',\n defaultSeverity: 'warn',\n check({ source, file, firstParty }) {\n if (firstParty) return []\n const out: Violation[] = []\n CREATE_TOKEN_LITERAL_REGEX.lastIndex = 0\n let match: RegExpExecArray | null\n while ((match = CREATE_TOKEN_LITERAL_REGEX.exec(source)) !== null) {\n const literal = match[1]\n if (!literal.startsWith('kick/')) continue\n out.push({\n ruleId: tokenReservedPrefix.id,\n severity: tokenReservedPrefix.defaultSeverity,\n file,\n line: lineOf(source, match.index),\n message: `\\`createToken('${literal}')\\` squats the reserved \\`kick/\\` prefix`,\n suggestion: `Pick an org-scoped prefix instead (e.g. \\`'mycorp/...'\\`)`,\n })\n }\n return out\n },\n}\n\n/** All rules shipped by this package, in run order. */\nexport const rules: readonly Rule[] = [diTokenSymbol, tokenKickPrefix, tokenReservedPrefix]\n"],"mappings":";;;;;;;;;;AAkCA,MAAM,kBAAoB,2DAGpB,2BACJ,4EAEF,SAAS,OAAO,OAAgB,MAAuB,CACrD,OAAO,OAAO,MAAM,EAAG,KAAK,CAAC,CAAC,MAAM;CAAI,CAAC,CAAC,MAC5C,CAWA,MAAa,cAAsB,CACjC,GAAI,kBACJ,YAAa,6DACb,gBAAiB,QACjB,MAAM,CAAE,OAAQ,MAAQ,CACtB,IAAM,IAAmB,CAAC,EACpB,MAAQ,OAAO,MAAM;CAAI,EAC/B,IAAK,IAAI,EAAI,EAAG,EAAI,MAAM,OAAQ,IAAK,CACrC,IAAM,KAAO,MAAM,GACnB,GAAI,KAAK,SAAS,sCAAsC,EAAG,SAC3D,kBAAkB,UAAY,EAC9B,IAAM,MAAQ,kBAAkB,KAAK,IAAI,EACpC,OACL,IAAI,KAAK,CACP,OAAQ,cAAc,GACtB,SAAU,cAAc,gBACxB,KACA,KAAM,EAAI,EACV,QAAS,KAAK,MAAM,GAAG,iDACvB,WAAY,6EACd,CAAC,CACH,CACA,OAAO,GACT,CACF,EAWa,gBAAwB,CACnC,GAAI,oBACJ,YAAa,oEACb,gBAAiB,QACjB,MAAM,CAAE,OAAQ,KAAM,YAAc,CAClC,GAAI,CAAC,WAAY,MAAO,CAAC,EACzB,IAAM,IAAmB,CAAC,EAC1B,2BAA2B,UAAY,EACvC,IAAI,MACJ,MAAQ,MAAQ,2BAA2B,KAAK,MAAM,KAAO,MAAM,CACjE,IAAM,QAAU,MAAM,GAClB,QAAQ,WAAW,OAAO,GAC1B,QAAQ,WAAW,SAAS,GAAK,QAAQ,WAAW,SAAS,GACjE,IAAI,KAAK,CACP,OAAQ,gBAAgB,GACxB,SAAU,gBAAgB,gBAC1B,KACA,KAAM,OAAO,OAAQ,MAAM,KAAK,EAChC,QAAS,kBAAkB,QAAQ,+CACnC,WAAY,qBAAqB,QAAQ,sDAC3C,CAAC,CACH,CACA,OAAO,GACT,CACF,EAQa,oBAA4B,CACvC,GAAI,wBACJ,YAAa,qEACb,gBAAiB,OACjB,MAAM,CAAE,OAAQ,KAAM,YAAc,CAClC,GAAI,WAAY,MAAO,CAAC,EACxB,IAAM,IAAmB,CAAC,EAC1B,2BAA2B,UAAY,EACvC,IAAI,MACJ,MAAQ,MAAQ,2BAA2B,KAAK,MAAM,KAAO,MAAM,CACjE,IAAM,QAAU,MAAM,GACjB,QAAQ,WAAW,OAAO,GAC/B,IAAI,KAAK,CACP,OAAQ,oBAAoB,GAC5B,SAAU,oBAAoB,gBAC9B,KACA,KAAM,OAAO,OAAQ,MAAM,KAAK,EAChC,QAAS,kBAAkB,QAAQ,2CACnC,WAAY,yDACd,CAAC,CACH,CACA,OAAO,GACT,CACF,EAGa,MAAyB,CAAC,cAAe,gBAAiB,mBAAmB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forinda/kickjs-lint",
3
- "version": "5.2.4",
3
+ "version": "5.2.6",
4
4
  "description": "Lint rules for KickJS — DI token convention, plugin/adapter naming, and other framework conventions",
5
5
  "keywords": [
6
6
  "kickjs",
@@ -35,9 +35,9 @@
35
35
  ],
36
36
  "dependencies": {},
37
37
  "devDependencies": {
38
- "@types/node": "^25.9.2",
39
- "typescript": "^6.0.3",
40
- "vitest": "^4.1.8"
38
+ "@types/node": "^26.6.5",
39
+ "typescript": "^7.0.2",
40
+ "vitest": "^5.0.3"
41
41
  },
42
42
  "publishConfig": {
43
43
  "access": "public"
@@ -60,7 +60,7 @@
60
60
  "build": "tsdown",
61
61
  "dev": "tsdown --watch",
62
62
  "test": "vitest run",
63
- "typecheck": "tsgo --noEmit",
63
+ "typecheck": "tsc --noEmit",
64
64
  "clean": "rm -rf dist .wireit"
65
65
  }
66
66
  }