@breadstone-infrastructure/token-linter 0.0.231
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/CHANGELOG.md +10 -0
- package/LICENSE +21 -0
- package/README.md +72 -0
- package/cli/Cli.d.ts +10 -0
- package/cli/Cli.d.ts.map +1 -0
- package/cli/Cli.js +32 -0
- package/cli/Cli.js.map +1 -0
- package/cli/CliArgs.d.ts +2 -0
- package/cli/CliArgs.d.ts.map +1 -0
- package/cli/CliArgs.js +88 -0
- package/cli/CliArgs.js.map +1 -0
- package/cli/CliArgsConfig.d.ts +2 -0
- package/cli/CliArgsConfig.d.ts.map +1 -0
- package/cli/CliArgsConfig.js +12 -0
- package/cli/CliArgsConfig.js.map +1 -0
- package/cli/CliRun.d.ts +2 -0
- package/cli/CliRun.d.ts.map +1 -0
- package/cli/CliRun.js +34 -0
- package/cli/CliRun.js.map +1 -0
- package/cli/commands/LintCommand.d.ts +2 -0
- package/cli/commands/LintCommand.d.ts.map +1 -0
- package/cli/commands/LintCommand.js +66 -0
- package/cli/commands/LintCommand.js.map +1 -0
- package/cli/commands/LintCommandArgsConfig.d.ts +2 -0
- package/cli/commands/LintCommandArgsConfig.d.ts.map +1 -0
- package/cli/commands/LintCommandArgsConfig.js +46 -0
- package/cli/commands/LintCommandArgsConfig.js.map +1 -0
- package/cli/lint.d.ts +3 -0
- package/cli/lint.d.ts.map +1 -0
- package/cli/lint.js +55 -0
- package/cli/lint.js.map +1 -0
- package/config/TokenLinterConfigDiscovery.d.ts +16 -0
- package/config/TokenLinterConfigDiscovery.d.ts.map +1 -0
- package/config/TokenLinterConfigDiscovery.js +44 -0
- package/config/TokenLinterConfigDiscovery.js.map +1 -0
- package/config/defineConfig.d.ts +41 -0
- package/config/defineConfig.d.ts.map +1 -0
- package/config/defineConfig.js +37 -0
- package/config/defineConfig.js.map +1 -0
- package/core/interaction.d.ts +25 -0
- package/core/interaction.d.ts.map +1 -0
- package/core/interaction.js +45 -0
- package/core/interaction.js.map +1 -0
- package/core/loader.d.ts +21 -0
- package/core/loader.d.ts.map +1 -0
- package/core/loader.js +84 -0
- package/core/loader.js.map +1 -0
- package/core/rule-registry.d.ts +46 -0
- package/core/rule-registry.d.ts.map +1 -0
- package/core/rule-registry.js +73 -0
- package/core/rule-registry.js.map +1 -0
- package/core/rule.d.ts +46 -0
- package/core/rule.d.ts.map +1 -0
- package/core/rule.js +3 -0
- package/core/rule.js.map +1 -0
- package/core/runner.d.ts +23 -0
- package/core/runner.d.ts.map +1 -0
- package/core/runner.js +103 -0
- package/core/runner.js.map +1 -0
- package/index.d.ts +46 -0
- package/index.d.ts.map +1 -0
- package/index.js +47 -0
- package/index.js.map +1 -0
- package/models/lint-result.d.ts +14 -0
- package/models/lint-result.d.ts.map +1 -0
- package/models/lint-result.js +3 -0
- package/models/lint-result.js.map +1 -0
- package/models/rule-context.d.ts +18 -0
- package/models/rule-context.d.ts.map +1 -0
- package/models/rule-context.js +3 -0
- package/models/rule-context.js.map +1 -0
- package/models/rule-finding.d.ts +15 -0
- package/models/rule-finding.d.ts.map +1 -0
- package/models/rule-finding.js +3 -0
- package/models/rule-finding.js.map +1 -0
- package/models/rule-result.d.ts +11 -0
- package/models/rule-result.d.ts.map +1 -0
- package/models/rule-result.js +3 -0
- package/models/rule-result.js.map +1 -0
- package/models/rule-severity.d.ts +14 -0
- package/models/rule-severity.d.ts.map +1 -0
- package/models/rule-severity.js +10 -0
- package/models/rule-severity.js.map +1 -0
- package/models/token-entry.d.ts +11 -0
- package/models/token-entry.d.ts.map +1 -0
- package/models/token-entry.js +2 -0
- package/models/token-entry.js.map +1 -0
- package/models/token-linter-config.d.ts +30 -0
- package/models/token-linter-config.d.ts.map +1 -0
- package/models/token-linter-config.js +3 -0
- package/models/token-linter-config.js.map +1 -0
- package/orchestration/TokenLinterOrchestrator.d.ts +29 -0
- package/orchestration/TokenLinterOrchestrator.d.ts.map +1 -0
- package/orchestration/TokenLinterOrchestrator.js +98 -0
- package/orchestration/TokenLinterOrchestrator.js.map +1 -0
- package/package.json +20 -0
- package/presets/index.d.ts +2 -0
- package/presets/index.d.ts.map +1 -0
- package/presets/index.js +2 -0
- package/presets/index.js.map +1 -0
- package/presets/recommended.d.ts +23 -0
- package/presets/recommended.d.ts.map +1 -0
- package/presets/recommended.js +41 -0
- package/presets/recommended.js.map +1 -0
- package/reporters/console.reporter.d.ts +15 -0
- package/reporters/console.reporter.d.ts.map +1 -0
- package/reporters/console.reporter.js +75 -0
- package/reporters/console.reporter.js.map +1 -0
- package/reporters/json.reporter.d.ts +11 -0
- package/reporters/json.reporter.d.ts.map +1 -0
- package/reporters/json.reporter.js +37 -0
- package/reporters/json.reporter.js.map +1 -0
- package/reporters/reporter.d.ts +16 -0
- package/reporters/reporter.d.ts.map +1 -0
- package/reporters/reporter.js +3 -0
- package/reporters/reporter.js.map +1 -0
- package/reporters/summary.reporter.d.ts +12 -0
- package/reporters/summary.reporter.d.ts.map +1 -0
- package/reporters/summary.reporter.js +48 -0
- package/reporters/summary.reporter.js.map +1 -0
- package/rules/alphabetical-sort.rule.d.ts +19 -0
- package/rules/alphabetical-sort.rule.d.ts.map +1 -0
- package/rules/alphabetical-sort.rule.js +72 -0
- package/rules/alphabetical-sort.rule.js.map +1 -0
- package/rules/component-presence.rule.d.ts +29 -0
- package/rules/component-presence.rule.d.ts.map +1 -0
- package/rules/component-presence.rule.js +102 -0
- package/rules/component-presence.rule.js.map +1 -0
- package/rules/duplicate-value.rule.d.ts +17 -0
- package/rules/duplicate-value.rule.d.ts.map +1 -0
- package/rules/duplicate-value.rule.js +49 -0
- package/rules/duplicate-value.rule.js.map +1 -0
- package/rules/empty-json.rule.d.ts +52 -0
- package/rules/empty-json.rule.d.ts.map +1 -0
- package/rules/empty-json.rule.js +100 -0
- package/rules/empty-json.rule.js.map +1 -0
- package/rules/forbidden-key.rule.d.ts +57 -0
- package/rules/forbidden-key.rule.d.ts.map +1 -0
- package/rules/forbidden-key.rule.js +118 -0
- package/rules/forbidden-key.rule.js.map +1 -0
- package/rules/forbidden-value.rule.d.ts +53 -0
- package/rules/forbidden-value.rule.d.ts.map +1 -0
- package/rules/forbidden-value.rule.js +130 -0
- package/rules/forbidden-value.rule.js.map +1 -0
- package/rules/includes-consistency.rule.d.ts +30 -0
- package/rules/includes-consistency.rule.d.ts.map +1 -0
- package/rules/includes-consistency.rule.js +153 -0
- package/rules/includes-consistency.rule.js.map +1 -0
- package/rules/index.d.ts +7 -0
- package/rules/index.d.ts.map +1 -0
- package/rules/index.js +47 -0
- package/rules/index.js.map +1 -0
- package/rules/key-consistency.rule.d.ts +38 -0
- package/rules/key-consistency.rule.d.ts.map +1 -0
- package/rules/key-consistency.rule.js +161 -0
- package/rules/key-consistency.rule.js.map +1 -0
- package/rules/missing-includes.rule.d.ts +19 -0
- package/rules/missing-includes.rule.d.ts.map +1 -0
- package/rules/missing-includes.rule.js +51 -0
- package/rules/missing-includes.rule.js.map +1 -0
- package/rules/mixin-reference.rule.d.ts +28 -0
- package/rules/mixin-reference.rule.d.ts.map +1 -0
- package/rules/mixin-reference.rule.js +104 -0
- package/rules/mixin-reference.rule.js.map +1 -0
- package/rules/naming-convention.rule.d.ts +29 -0
- package/rules/naming-convention.rule.d.ts.map +1 -0
- package/rules/naming-convention.rule.js +112 -0
- package/rules/naming-convention.rule.js.map +1 -0
- package/rules/nested-depth.rule.d.ts +42 -0
- package/rules/nested-depth.rule.d.ts.map +1 -0
- package/rules/nested-depth.rule.js +72 -0
- package/rules/nested-depth.rule.js.map +1 -0
- package/rules/required-includes.rule.d.ts +60 -0
- package/rules/required-includes.rule.d.ts.map +1 -0
- package/rules/required-includes.rule.js +118 -0
- package/rules/required-includes.rule.js.map +1 -0
- package/rules/rule-options.d.ts +158 -0
- package/rules/rule-options.d.ts.map +1 -0
- package/rules/rule-options.js +89 -0
- package/rules/rule-options.js.map +1 -0
- package/rules/value-consistency.rule.d.ts +17 -0
- package/rules/value-consistency.rule.d.ts.map +1 -0
- package/rules/value-consistency.rule.js +69 -0
- package/rules/value-consistency.rule.js.map +1 -0
- package/rules/value-schema.rule.d.ts +18 -0
- package/rules/value-schema.rule.d.ts.map +1 -0
- package/rules/value-schema.rule.js +108 -0
- package/rules/value-schema.rule.js.map +1 -0
- package/rules/value-type.rule.d.ts +71 -0
- package/rules/value-type.rule.d.ts.map +1 -0
- package/rules/value-type.rule.js +176 -0
- package/rules/value-type.rule.js.map +1 -0
- package/utils.d.ts +55 -0
- package/utils.d.ts.map +1 -0
- package/utils.js +125 -0
- package/utils.js.map +1 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// #region Imports
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import { promptSelect, SKIP, SKIP_ALL } from '../core/interaction.js';
|
|
4
|
+
import { RuleSeverity } from '../models/rule-severity.js';
|
|
5
|
+
// #endregion
|
|
6
|
+
/**
|
|
7
|
+
* @description Validates that $includes references point to existing mixin files.
|
|
8
|
+
*
|
|
9
|
+
* @example Interactive prompt
|
|
10
|
+
* ```
|
|
11
|
+
* ? Mixin 'glow' (base: 'glow') referenced in '$includes' does not exist as a mixin file.
|
|
12
|
+
* > Remove 'glow' from $includes
|
|
13
|
+
* Skip
|
|
14
|
+
* Skip all remaining
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* @public
|
|
18
|
+
*/
|
|
19
|
+
export class MixinReferenceRule {
|
|
20
|
+
// #region Fields
|
|
21
|
+
name = 'mixin-reference';
|
|
22
|
+
description = 'Validates that $includes references point to existing mixin files.';
|
|
23
|
+
defaultSeverity = RuleSeverity.Error;
|
|
24
|
+
fixable = false;
|
|
25
|
+
interactive = true;
|
|
26
|
+
// #endregion
|
|
27
|
+
// #region Methods
|
|
28
|
+
run(context) {
|
|
29
|
+
const findings = [];
|
|
30
|
+
for (const entry of context.entries) {
|
|
31
|
+
const includes = entry.content['$includes'];
|
|
32
|
+
if (!Array.isArray(includes)) {
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
for (const mixin of includes) {
|
|
36
|
+
// Mixins can have modifiers like "color:background" — extract the base mixin name
|
|
37
|
+
const baseMixinName = mixin.split(':')[0];
|
|
38
|
+
if (!context.mixinNames.includes(baseMixinName)) {
|
|
39
|
+
findings.push({
|
|
40
|
+
component: entry.component,
|
|
41
|
+
theme: entry.theme,
|
|
42
|
+
key: '$includes',
|
|
43
|
+
message: `Mixin '${mixin}' (base: '${baseMixinName}') referenced in '$includes' does not exist as a mixin file.`,
|
|
44
|
+
severity: this.defaultSeverity,
|
|
45
|
+
filePath: entry.filePath,
|
|
46
|
+
fixed: false,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return { ruleName: this.name, findings, duration: 0 };
|
|
52
|
+
}
|
|
53
|
+
async interact(context, findings) {
|
|
54
|
+
const entryMap = new Map(context.entries.map((e) => [`${e.theme}:${e.component}`, e]));
|
|
55
|
+
const pendingWrites = new Map();
|
|
56
|
+
const result = [];
|
|
57
|
+
let skipAll = false;
|
|
58
|
+
for (const finding of findings) {
|
|
59
|
+
if (skipAll) {
|
|
60
|
+
result.push(finding);
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (!finding.filePath) {
|
|
64
|
+
result.push(finding);
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
const entry = entryMap.get(`${finding.theme}:${finding.component}`);
|
|
68
|
+
if (!entry) {
|
|
69
|
+
result.push(finding);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
const mixinMatch = finding.message.match(/^Mixin '(.+?)'/);
|
|
73
|
+
const mixinToRemove = mixinMatch?.[1];
|
|
74
|
+
if (!mixinToRemove) {
|
|
75
|
+
result.push(finding);
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
const action = await promptSelect(finding.message, [`Remove '${mixinToRemove}' from $includes`]);
|
|
79
|
+
if (action === SKIP_ALL) {
|
|
80
|
+
skipAll = true;
|
|
81
|
+
result.push(finding);
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (action === SKIP) {
|
|
85
|
+
result.push(finding);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
const includes = entry.content['$includes'];
|
|
89
|
+
if (Array.isArray(includes)) {
|
|
90
|
+
entry.content['$includes'] = includes.filter(m => m !== mixinToRemove);
|
|
91
|
+
pendingWrites.set(entry.filePath, entry);
|
|
92
|
+
result.push({ ...finding, fixed: true });
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
result.push(finding);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
for (const entry of pendingWrites.values()) {
|
|
99
|
+
fs.writeFileSync(entry.filePath, JSON.stringify(entry.content, null, 2) + '\n', 'utf-8');
|
|
100
|
+
}
|
|
101
|
+
return result;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=mixin-reference.rule.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mixin-reference.rule.js","sourceRoot":"","sources":["../../src/rules/mixin-reference.rule.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { IRule } from '../core/rule.js';
|
|
2
|
+
import type { IRuleContext } from '../models/rule-context.js';
|
|
3
|
+
import type { IRuleFinding } from '../models/rule-finding.js';
|
|
4
|
+
import type { IRuleResult } from '../models/rule-result.js';
|
|
5
|
+
import { RuleSeverity } from '../models/rule-severity.js';
|
|
6
|
+
/**
|
|
7
|
+
* @description Enforces camelCase naming convention for token keys.
|
|
8
|
+
*
|
|
9
|
+
* @example Interactive prompt
|
|
10
|
+
* ```
|
|
11
|
+
* ? Key 'border_radius' does not follow camelCase naming convention.
|
|
12
|
+
* > Rename to 'borderRadius'
|
|
13
|
+
* Skip
|
|
14
|
+
* Skip all remaining
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* @public
|
|
18
|
+
*/
|
|
19
|
+
export declare class NamingConventionRule implements IRule {
|
|
20
|
+
readonly name = "naming-convention";
|
|
21
|
+
readonly description = "Enforces camelCase naming convention for token keys.";
|
|
22
|
+
readonly defaultSeverity: RuleSeverity;
|
|
23
|
+
readonly fixable = false;
|
|
24
|
+
readonly interactive = true;
|
|
25
|
+
run(context: IRuleContext): IRuleResult;
|
|
26
|
+
interact(context: IRuleContext, findings: ReadonlyArray<IRuleFinding>): Promise<IRuleFinding[]>;
|
|
27
|
+
private _toCamelCase;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=naming-convention.rule.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"naming-convention.rule.d.ts","sourceRoot":"","sources":["../../src/rules/naming-convention.rule.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAY1D;;;;;;;;;;;;GAYG;AACH,qBAAa,oBAAqB,YAAW,KAAK;IAI9C,SAAgB,IAAI,uBAAuB;IAC3C,SAAgB,WAAW,0DAA0D;IACrF,SAAgB,eAAe,EAAE,YAAY,CAAwB;IACrE,SAAgB,OAAO,SAAS;IAChC,SAAgB,WAAW,QAAQ;IAM5B,GAAG,CAAC,OAAO,EAAE,YAAY,GAAG,WAAW;IAwBjC,QAAQ,CAAC,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,aAAa,CAAC,YAAY,CAAC,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IA4E5G,OAAO,CAAC,YAAY;CAOvB"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// #region Imports
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import { promptSelect, SKIP, SKIP_ALL } from '../core/interaction.js';
|
|
4
|
+
import { RuleSeverity } from '../models/rule-severity.js';
|
|
5
|
+
import { getTokenKeys } from '../utils.js';
|
|
6
|
+
// #endregion
|
|
7
|
+
// #region Constants
|
|
8
|
+
const CAMEL_CASE_PATTERN = /^[a-z][a-zA-Z0-9]*$/;
|
|
9
|
+
// #endregion
|
|
10
|
+
/**
|
|
11
|
+
* @description Enforces camelCase naming convention for token keys.
|
|
12
|
+
*
|
|
13
|
+
* @example Interactive prompt
|
|
14
|
+
* ```
|
|
15
|
+
* ? Key 'border_radius' does not follow camelCase naming convention.
|
|
16
|
+
* > Rename to 'borderRadius'
|
|
17
|
+
* Skip
|
|
18
|
+
* Skip all remaining
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* @public
|
|
22
|
+
*/
|
|
23
|
+
export class NamingConventionRule {
|
|
24
|
+
// #region Fields
|
|
25
|
+
name = 'naming-convention';
|
|
26
|
+
description = 'Enforces camelCase naming convention for token keys.';
|
|
27
|
+
defaultSeverity = RuleSeverity.Warning;
|
|
28
|
+
fixable = false;
|
|
29
|
+
interactive = true;
|
|
30
|
+
// #endregion
|
|
31
|
+
// #region Methods
|
|
32
|
+
run(context) {
|
|
33
|
+
const findings = [];
|
|
34
|
+
for (const entry of context.entries) {
|
|
35
|
+
const keys = getTokenKeys(entry.content);
|
|
36
|
+
for (const key of keys) {
|
|
37
|
+
if (!CAMEL_CASE_PATTERN.test(key)) {
|
|
38
|
+
findings.push({
|
|
39
|
+
component: entry.component,
|
|
40
|
+
theme: entry.theme,
|
|
41
|
+
key,
|
|
42
|
+
message: `Key '${key}' does not follow camelCase naming convention.`,
|
|
43
|
+
severity: this.defaultSeverity,
|
|
44
|
+
filePath: entry.filePath,
|
|
45
|
+
fixed: false,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return { ruleName: this.name, findings, duration: 0 };
|
|
51
|
+
}
|
|
52
|
+
async interact(context, findings) {
|
|
53
|
+
const entryMap = new Map(context.entries.map((e) => [`${e.theme}:${e.component}`, e]));
|
|
54
|
+
const pendingWrites = new Map();
|
|
55
|
+
const result = [];
|
|
56
|
+
let skipAll = false;
|
|
57
|
+
for (const finding of findings) {
|
|
58
|
+
if (skipAll) {
|
|
59
|
+
result.push(finding);
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (!finding.key || !finding.filePath) {
|
|
63
|
+
result.push(finding);
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
const entry = entryMap.get(`${finding.theme}:${finding.component}`);
|
|
67
|
+
if (!entry) {
|
|
68
|
+
result.push(finding);
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const suggested = this._toCamelCase(finding.key);
|
|
72
|
+
if (suggested === finding.key) {
|
|
73
|
+
result.push(finding);
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
const choices = [{
|
|
77
|
+
name: 'rename',
|
|
78
|
+
message: `Rename to '${suggested}'`,
|
|
79
|
+
}];
|
|
80
|
+
const action = await promptSelect(finding.message, choices);
|
|
81
|
+
if (action === SKIP_ALL) {
|
|
82
|
+
skipAll = true;
|
|
83
|
+
result.push(finding);
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (action === SKIP) {
|
|
87
|
+
result.push(finding);
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (action === 'rename' && Object.prototype.hasOwnProperty.call(entry.content, finding.key)) {
|
|
91
|
+
const value = entry.content[finding.key];
|
|
92
|
+
delete entry.content[finding.key];
|
|
93
|
+
entry.content[suggested] = value;
|
|
94
|
+
pendingWrites.set(entry.filePath, entry);
|
|
95
|
+
result.push({ ...finding, fixed: true });
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
result.push(finding);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
for (const entry of pendingWrites.values()) {
|
|
102
|
+
fs.writeFileSync(entry.filePath, JSON.stringify(entry.content, null, 2) + '\n', 'utf-8');
|
|
103
|
+
}
|
|
104
|
+
return result;
|
|
105
|
+
}
|
|
106
|
+
_toCamelCase(str) {
|
|
107
|
+
return str
|
|
108
|
+
.replace(/[-_]+(.)/g, (_, c) => c.toUpperCase())
|
|
109
|
+
.replace(/^[A-Z]/, c => c.toLowerCase());
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=naming-convention.rule.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"naming-convention.rule.js","sourceRoot":"","sources":["../../src/rules/naming-convention.rule.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { IRule } from '../core/rule.js';
|
|
2
|
+
import type { IRuleContext } from '../models/rule-context.js';
|
|
3
|
+
import type { IRuleResult } from '../models/rule-result.js';
|
|
4
|
+
import { RuleSeverity } from '../models/rule-severity.js';
|
|
5
|
+
import type { IRuleOptionValues, IRuleOptionsEntry } from '../models/token-linter-config.js';
|
|
6
|
+
/**
|
|
7
|
+
* @description Options for the `nested-depth` rule.
|
|
8
|
+
* @public
|
|
9
|
+
*/
|
|
10
|
+
export interface INestedDepthRuleOptions extends IRuleOptionValues {
|
|
11
|
+
/**
|
|
12
|
+
* @description Maximum allowed nesting depth for token objects.
|
|
13
|
+
* @default 2
|
|
14
|
+
*/
|
|
15
|
+
readonly maxDepth?: number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* @description Creates typed rule options for the `nested-depth` rule.
|
|
19
|
+
* @public
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```js
|
|
23
|
+
* ruleOptions: {
|
|
24
|
+
* ...nestedDepthRuleOptions({ maxDepth: 3 }),
|
|
25
|
+
* }
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export declare function nestedDepthRuleOptions(options: INestedDepthRuleOptions): IRuleOptionsEntry;
|
|
29
|
+
/**
|
|
30
|
+
* @description Limits the nesting depth of token objects to prevent overly complex structures.
|
|
31
|
+
* @public
|
|
32
|
+
*/
|
|
33
|
+
export declare class NestedDepthRule implements IRule {
|
|
34
|
+
readonly name = "nested-depth";
|
|
35
|
+
readonly description = "Limits the nesting depth of token objects.";
|
|
36
|
+
readonly defaultSeverity: RuleSeverity;
|
|
37
|
+
readonly fixable = false;
|
|
38
|
+
readonly interactive = false;
|
|
39
|
+
run(context: IRuleContext): IRuleResult;
|
|
40
|
+
private checkDepth;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=nested-depth.rule.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nested-depth.rule.d.ts","sourceRoot":"","sources":["../../src/rules/nested-depth.rule.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAE9D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC1D,OAAO,KAAK,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AAa7F;;;GAGG;AACH,MAAM,WAAW,uBAAwB,SAAQ,iBAAiB;IAE9D;;;OAGG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC9B;AAMD;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,uBAAuB,GAAG,iBAAiB,CAE1F;AAID;;;GAGG;AACH,qBAAa,eAAgB,YAAW,KAAK;IAIzC,SAAgB,IAAI,kBAAkB;IACtC,SAAgB,WAAW,gDAAgD;IAC3E,SAAgB,eAAe,EAAE,YAAY,CAAwB;IACrE,SAAgB,OAAO,SAAS;IAChC,SAAgB,WAAW,SAAS;IAM7B,GAAG,CAAC,OAAO,EAAE,YAAY,GAAG,WAAW;IAY9C,OAAO,CAAC,UAAU;CAoCrB"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// #region Imports
|
|
2
|
+
import { RuleSeverity } from '../models/rule-severity.js';
|
|
3
|
+
import { METADATA_KEYS } from '../utils.js';
|
|
4
|
+
// #endregion
|
|
5
|
+
// #region Constants
|
|
6
|
+
const DEFAULT_MAX_DEPTH = 2;
|
|
7
|
+
// #endregion
|
|
8
|
+
// #region Functions
|
|
9
|
+
/**
|
|
10
|
+
* @description Creates typed rule options for the `nested-depth` rule.
|
|
11
|
+
* @public
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```js
|
|
15
|
+
* ruleOptions: {
|
|
16
|
+
* ...nestedDepthRuleOptions({ maxDepth: 3 }),
|
|
17
|
+
* }
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export function nestedDepthRuleOptions(options) {
|
|
21
|
+
return { 'nested-depth': options };
|
|
22
|
+
}
|
|
23
|
+
// #endregion
|
|
24
|
+
/**
|
|
25
|
+
* @description Limits the nesting depth of token objects to prevent overly complex structures.
|
|
26
|
+
* @public
|
|
27
|
+
*/
|
|
28
|
+
export class NestedDepthRule {
|
|
29
|
+
// #region Fields
|
|
30
|
+
name = 'nested-depth';
|
|
31
|
+
description = 'Limits the nesting depth of token objects.';
|
|
32
|
+
defaultSeverity = RuleSeverity.Warning;
|
|
33
|
+
fixable = false;
|
|
34
|
+
interactive = false;
|
|
35
|
+
// #endregion
|
|
36
|
+
// #region Methods
|
|
37
|
+
run(context) {
|
|
38
|
+
const findings = [];
|
|
39
|
+
const options = context.config.ruleOptions[this.name];
|
|
40
|
+
const maxDepth = options?.maxDepth ?? DEFAULT_MAX_DEPTH;
|
|
41
|
+
for (const entry of context.entries) {
|
|
42
|
+
this.checkDepth(entry.content, 0, maxDepth, '', entry, findings);
|
|
43
|
+
}
|
|
44
|
+
return { ruleName: this.name, findings, duration: 0 };
|
|
45
|
+
}
|
|
46
|
+
checkDepth(obj, currentDepth, maxDepth, path, entry, findings) {
|
|
47
|
+
for (const [key, val] of Object.entries(obj)) {
|
|
48
|
+
if (METADATA_KEYS.includes(key)) {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
const fullPath = path ? `${path}.${key}` : key;
|
|
52
|
+
if (typeof val === 'object' && val !== null && !('value' in val)) {
|
|
53
|
+
const nextDepth = currentDepth + 1;
|
|
54
|
+
if (nextDepth > maxDepth) {
|
|
55
|
+
findings.push({
|
|
56
|
+
component: entry.component,
|
|
57
|
+
theme: entry.theme,
|
|
58
|
+
key: fullPath,
|
|
59
|
+
message: `Nesting depth ${nextDepth} exceeds maximum of ${maxDepth} at '${fullPath}'.`,
|
|
60
|
+
severity: this.defaultSeverity,
|
|
61
|
+
filePath: entry.filePath,
|
|
62
|
+
fixed: false,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
this.checkDepth(val, nextDepth, maxDepth, fullPath, entry, findings);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=nested-depth.rule.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nested-depth.rule.js","sourceRoot":"","sources":["../../src/rules/nested-depth.rule.ts"],"names":[],"mappings":"AAAA,kBAAkB;AAMlB,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAE1D,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,aAAa;AAEb,oBAAoB;AAEpB,MAAM,iBAAiB,GAAG,CAAC,CAAC;AAmB5B,aAAa;AAEb,oBAAoB;AAEpB;;;;;;;;;;GAUG;AACH,MAAM,UAAU,sBAAsB,CAAC,OAAgC;IACnE,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,CAAC;AACvC,CAAC;AAED,aAAa;AAEb;;;GAGG;AACH,MAAM,OAAO,eAAe;IAExB,iBAAiB;IAED,IAAI,GAAG,cAAc,CAAC;IACtB,WAAW,GAAG,4CAA4C,CAAC;IAC3D,eAAe,GAAiB,YAAY,CAAC,OAAO,CAAC;IACrD,OAAO,GAAG,KAAK,CAAC;IAChB,WAAW,GAAG,KAAK,CAAC;IAEpC,aAAa;IAEb,kBAAkB;IAEX,GAAG,CAAC,OAAqB;QAC5B,MAAM,QAAQ,GAAmB,EAAE,CAAC;QACpC,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAwC,CAAC;QAC7F,MAAM,QAAQ,GAAG,OAAO,EAAE,QAAQ,IAAI,iBAAiB,CAAC;QAExD,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;YAClC,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC;QACrE,CAAC;QAED,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;IAC1D,CAAC;IAEO,UAAU,CACd,GAA4B,EAC5B,YAAoB,EACpB,QAAgB,EAChB,IAAY,EACZ,KAA6D,EAC7D,QAAwB;QAExB,KAAK,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;YAC3C,IAAI,aAAa,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC9B,SAAS;YACb,CAAC;YAED,MAAM,QAAQ,GAAG,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC;YAE/C,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI,IAAI,CAAC,CAAC,OAAO,IAAI,GAAG,CAAC,EAAE,CAAC;gBAC/D,MAAM,SAAS,GAAG,YAAY,GAAG,CAAC,CAAC;gBAEnC,IAAI,SAAS,GAAG,QAAQ,EAAE,CAAC;oBACvB,QAAQ,CAAC,IAAI,CAAC;wBACV,SAAS,EAAE,KAAK,CAAC,SAAS;wBAC1B,KAAK,EAAE,KAAK,CAAC,KAAK;wBAClB,GAAG,EAAE,QAAQ;wBACb,OAAO,EAAE,iBAAiB,SAAS,uBAAuB,QAAQ,QAAQ,QAAQ,IAAI;wBACtF,QAAQ,EAAE,IAAI,CAAC,eAAe;wBAC9B,QAAQ,EAAE,KAAK,CAAC,QAAQ;wBACxB,KAAK,EAAE,KAAK;qBACf,CAAC,CAAC;gBACP,CAAC;qBAAM,CAAC;oBACJ,IAAI,CAAC,UAAU,CAAC,GAA8B,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC;gBACpG,CAAC;YACL,CAAC;QACL,CAAC;IACL,CAAC;CAGJ"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { IRule } from '../core/rule.js';
|
|
2
|
+
import type { IRuleContext } from '../models/rule-context.js';
|
|
3
|
+
import type { IRuleFinding } from '../models/rule-finding.js';
|
|
4
|
+
import type { IRuleResult } from '../models/rule-result.js';
|
|
5
|
+
import { RuleSeverity } from '../models/rule-severity.js';
|
|
6
|
+
import type { IRuleOptionValues, IRuleOptionsEntry } from '../models/token-linter-config.js';
|
|
7
|
+
/**
|
|
8
|
+
* @description Options for the `required-includes` rule.
|
|
9
|
+
* @public
|
|
10
|
+
*/
|
|
11
|
+
export interface IRequiredIncludesRuleOptions extends IRuleOptionValues {
|
|
12
|
+
/**
|
|
13
|
+
* @description List of mixin names that every component must include. Overrides the built-in default list.
|
|
14
|
+
* @default ['font', 'padding', 'gap', 'transition', 'translate', 'shadow']
|
|
15
|
+
*/
|
|
16
|
+
readonly includes?: ReadonlyArray<string>;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* @description Creates typed rule options for the `required-includes` rule.
|
|
20
|
+
* @public
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```js
|
|
24
|
+
* ruleOptions: {
|
|
25
|
+
* ...requiredIncludesRuleOptions({ includes: ['font', 'padding', 'gap'] }),
|
|
26
|
+
* }
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export declare function requiredIncludesRuleOptions(options: IRequiredIncludesRuleOptions): IRuleOptionsEntry;
|
|
30
|
+
/**
|
|
31
|
+
* @description Ensures that every component token file contains a predefined set of required `$includes` entries.
|
|
32
|
+
* Missing entries are reported as findings. When auto-fix is enabled, missing entries are appended
|
|
33
|
+
* to the `$includes` array and the file is rewritten.
|
|
34
|
+
*
|
|
35
|
+
* The required list defaults to `['focusRing']` and can be overridden via `ruleOptions`:
|
|
36
|
+
*
|
|
37
|
+
* @example Configuration
|
|
38
|
+
* ```js
|
|
39
|
+
* export default defineConfig({
|
|
40
|
+
* rules: { 'required-includes': 'error' },
|
|
41
|
+
* ruleOptions: {
|
|
42
|
+
* 'required-includes': {
|
|
43
|
+
* includes: ['focusRing', 'ripple', 'transition'],
|
|
44
|
+
* },
|
|
45
|
+
* },
|
|
46
|
+
* });
|
|
47
|
+
* ```
|
|
48
|
+
*
|
|
49
|
+
* @public
|
|
50
|
+
*/
|
|
51
|
+
export declare class RequiredIncludesRule implements IRule {
|
|
52
|
+
readonly name = "required-includes";
|
|
53
|
+
readonly description = "Ensures every component includes a predefined set of required $includes entries.";
|
|
54
|
+
readonly defaultSeverity: RuleSeverity;
|
|
55
|
+
readonly fixable = true;
|
|
56
|
+
readonly interactive = false;
|
|
57
|
+
run(context: IRuleContext): IRuleResult;
|
|
58
|
+
fix(context: IRuleContext, findings: ReadonlyArray<IRuleFinding>): IRuleFinding[];
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=required-includes.rule.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"required-includes.rule.d.ts","sourceRoot":"","sources":["../../src/rules/required-includes.rule.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC1D,OAAO,KAAK,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AAmB7F;;;GAGG;AACH,MAAM,WAAW,4BAA6B,SAAQ,iBAAiB;IAEnE;;;OAGG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CAC7C;AAMD;;;;;;;;;;GAUG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,4BAA4B,GAAG,iBAAiB,CAEpG;AAID;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,oBAAqB,YAAW,KAAK;IAI9C,SAAgB,IAAI,uBAAuB;IAC3C,SAAgB,WAAW,sFAAsF;IACjH,SAAgB,eAAe,EAAE,YAAY,CAAsB;IACnE,SAAgB,OAAO,QAAQ;IAC/B,SAAgB,WAAW,SAAS;IAM7B,GAAG,CAAC,OAAO,EAAE,YAAY,GAAG,WAAW;IA4BvC,GAAG,CAAC,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,aAAa,CAAC,YAAY,CAAC,GAAG,YAAY,EAAE;CA6C3F"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// #region Imports
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import { RuleSeverity } from '../models/rule-severity.js';
|
|
4
|
+
// #endregion
|
|
5
|
+
// #region Constants
|
|
6
|
+
const DEFAULT_REQUIRED_INCLUDES = [
|
|
7
|
+
'font',
|
|
8
|
+
'padding',
|
|
9
|
+
'gap',
|
|
10
|
+
'transition',
|
|
11
|
+
'translate',
|
|
12
|
+
'shadow'
|
|
13
|
+
];
|
|
14
|
+
// #endregion
|
|
15
|
+
// #region Functions
|
|
16
|
+
/**
|
|
17
|
+
* @description Creates typed rule options for the `required-includes` rule.
|
|
18
|
+
* @public
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```js
|
|
22
|
+
* ruleOptions: {
|
|
23
|
+
* ...requiredIncludesRuleOptions({ includes: ['font', 'padding', 'gap'] }),
|
|
24
|
+
* }
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export function requiredIncludesRuleOptions(options) {
|
|
28
|
+
return { 'required-includes': options };
|
|
29
|
+
}
|
|
30
|
+
// #endregion
|
|
31
|
+
/**
|
|
32
|
+
* @description Ensures that every component token file contains a predefined set of required `$includes` entries.
|
|
33
|
+
* Missing entries are reported as findings. When auto-fix is enabled, missing entries are appended
|
|
34
|
+
* to the `$includes` array and the file is rewritten.
|
|
35
|
+
*
|
|
36
|
+
* The required list defaults to `['focusRing']` and can be overridden via `ruleOptions`:
|
|
37
|
+
*
|
|
38
|
+
* @example Configuration
|
|
39
|
+
* ```js
|
|
40
|
+
* export default defineConfig({
|
|
41
|
+
* rules: { 'required-includes': 'error' },
|
|
42
|
+
* ruleOptions: {
|
|
43
|
+
* 'required-includes': {
|
|
44
|
+
* includes: ['focusRing', 'ripple', 'transition'],
|
|
45
|
+
* },
|
|
46
|
+
* },
|
|
47
|
+
* });
|
|
48
|
+
* ```
|
|
49
|
+
*
|
|
50
|
+
* @public
|
|
51
|
+
*/
|
|
52
|
+
export class RequiredIncludesRule {
|
|
53
|
+
// #region Fields
|
|
54
|
+
name = 'required-includes';
|
|
55
|
+
description = 'Ensures every component includes a predefined set of required $includes entries.';
|
|
56
|
+
defaultSeverity = RuleSeverity.Error;
|
|
57
|
+
fixable = true;
|
|
58
|
+
interactive = false;
|
|
59
|
+
// #endregion
|
|
60
|
+
// #region Methods
|
|
61
|
+
run(context) {
|
|
62
|
+
const findings = [];
|
|
63
|
+
const options = context.config.ruleOptions[this.name];
|
|
64
|
+
const requiredIncludes = options?.includes ?? DEFAULT_REQUIRED_INCLUDES;
|
|
65
|
+
for (const entry of context.entries) {
|
|
66
|
+
const includes = entry.content['$includes'];
|
|
67
|
+
const currentIncludes = Array.isArray(includes) ? includes : [];
|
|
68
|
+
const currentBases = new Set(currentIncludes.map(i => i.split(':')[0]));
|
|
69
|
+
for (const required of requiredIncludes) {
|
|
70
|
+
if (!currentBases.has(required)) {
|
|
71
|
+
findings.push({
|
|
72
|
+
component: entry.component,
|
|
73
|
+
theme: entry.theme,
|
|
74
|
+
key: '$includes',
|
|
75
|
+
message: `Required include '${required}' is missing in component '${entry.component}' (${entry.theme}).`,
|
|
76
|
+
severity: this.defaultSeverity,
|
|
77
|
+
filePath: entry.filePath,
|
|
78
|
+
fixed: false,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return { ruleName: this.name, findings, duration: 0 };
|
|
84
|
+
}
|
|
85
|
+
fix(context, findings) {
|
|
86
|
+
const entryMap = new Map(context.entries.map(e => [e.filePath, e]));
|
|
87
|
+
const pendingWrites = new Map();
|
|
88
|
+
const result = [];
|
|
89
|
+
for (const finding of findings) {
|
|
90
|
+
const entry = finding.filePath ? entryMap.get(finding.filePath) : undefined;
|
|
91
|
+
if (!entry) {
|
|
92
|
+
result.push(finding);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
const missingInclude = finding.message.match(/Required include '(.+?)'/)?.[1];
|
|
96
|
+
if (!missingInclude) {
|
|
97
|
+
result.push(finding);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
const content = pendingWrites.get(entry.filePath) ?? entry.content;
|
|
101
|
+
const currentIncludes = Array.isArray(content['$includes'])
|
|
102
|
+
? [...content['$includes']]
|
|
103
|
+
: [];
|
|
104
|
+
if (!currentIncludes.some(i => i.split(':')[0] === missingInclude)) {
|
|
105
|
+
currentIncludes.push(missingInclude);
|
|
106
|
+
currentIncludes.sort((a, b) => a.localeCompare(b));
|
|
107
|
+
}
|
|
108
|
+
content['$includes'] = currentIncludes;
|
|
109
|
+
pendingWrites.set(entry.filePath, content);
|
|
110
|
+
result.push({ ...finding, fixed: true });
|
|
111
|
+
}
|
|
112
|
+
for (const [filePath, content] of pendingWrites.entries()) {
|
|
113
|
+
fs.writeFileSync(filePath, JSON.stringify(content, null, 2) + '\n', 'utf-8');
|
|
114
|
+
}
|
|
115
|
+
return result;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=required-includes.rule.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"required-includes.rule.js","sourceRoot":"","sources":["../../src/rules/required-includes.rule.ts"],"names":[],"mappings":"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"}
|