@reteps/tree-sitter-htmlmustache 0.9.1 → 0.9.2
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/browser/out/browser/index.d.ts +11 -2
- package/browser/out/browser/index.d.ts.map +1 -1
- package/browser/out/browser/index.mjs.map +2 -2
- package/browser/out/core/configSchema.d.ts +8 -0
- package/browser/out/core/configSchema.d.ts.map +1 -1
- package/browser/out/core/customRuleFilter.d.ts +14 -0
- package/browser/out/core/customRuleFilter.d.ts.map +1 -0
- package/cli/out/main.js +39 -9
- package/package.json +1 -1
- package/src/browser/browser.test.ts +14 -0
- package/src/browser/index.ts +11 -2
- package/src/core/configSchema.ts +16 -0
- package/src/core/customRuleFilter.ts +32 -0
|
@@ -34,6 +34,14 @@ export interface CustomRule {
|
|
|
34
34
|
selector: string;
|
|
35
35
|
message: string;
|
|
36
36
|
severity?: RuleSeverity;
|
|
37
|
+
/**
|
|
38
|
+
* Optional glob patterns (relative to the config file) restricting which
|
|
39
|
+
* files this rule applies to. Applied as an additional filter on top of the
|
|
40
|
+
* top-level `include`/`exclude` — a rule only fires for files that both
|
|
41
|
+
* the top-level settings include AND the per-rule settings include.
|
|
42
|
+
*/
|
|
43
|
+
include?: string[];
|
|
44
|
+
exclude?: string[];
|
|
37
45
|
}
|
|
38
46
|
export interface NoBreakDelimiter {
|
|
39
47
|
start: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"configSchema.d.ts","sourceRoot":"","sources":["../../../src/core/configSchema.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAA2B,MAAM,qBAAqB,CAAC;AAWxF,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,SAAS,GAAG,KAAK,CAAC;AAEvD,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,EAAE,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACpD;AAED,MAAM,MAAM,SAAS,GAAG,YAAY,GAAG;IAAE,QAAQ,EAAE,YAAY,CAAA;CAAE,CAAC;AAClE,MAAM,MAAM,oBAAoB,CAAC,QAAQ,IAAI,YAAY,GAAG,CAAC;IAAE,QAAQ,EAAE,YAAY,CAAA;CAAE,GAAG,QAAQ,CAAC,CAAC;AAEpG,MAAM,WAAW,WAAW;IAC1B,uBAAuB,CAAC,EAAE,SAAS,CAAC;IACpC,0BAA0B,CAAC,EAAE,SAAS,CAAC;IACvC,4BAA4B,CAAC,EAAE,SAAS,CAAC;IACzC,sBAAsB,CAAC,EAAE,SAAS,CAAC;IACnC,mBAAmB,CAAC,EAAE,SAAS,CAAC;IAChC,iBAAiB,CAAC,EAAE,SAAS,CAAC;IAC9B,sBAAsB,CAAC,EAAE,SAAS,CAAC;IACnC,oBAAoB,CAAC,EAAE,SAAS,CAAC;IACjC,qBAAqB,CAAC,EAAE,oBAAoB,CAAC,4BAA4B,CAAC,CAAC;CAC5E;AAuCD,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"configSchema.d.ts","sourceRoot":"","sources":["../../../src/core/configSchema.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAA2B,MAAM,qBAAqB,CAAC;AAWxF,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,SAAS,GAAG,KAAK,CAAC;AAEvD,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,EAAE,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACpD;AAED,MAAM,MAAM,SAAS,GAAG,YAAY,GAAG;IAAE,QAAQ,EAAE,YAAY,CAAA;CAAE,CAAC;AAClE,MAAM,MAAM,oBAAoB,CAAC,QAAQ,IAAI,YAAY,GAAG,CAAC;IAAE,QAAQ,EAAE,YAAY,CAAA;CAAE,GAAG,QAAQ,CAAC,CAAC;AAEpG,MAAM,WAAW,WAAW;IAC1B,uBAAuB,CAAC,EAAE,SAAS,CAAC;IACpC,0BAA0B,CAAC,EAAE,SAAS,CAAC;IACvC,4BAA4B,CAAC,EAAE,SAAS,CAAC;IACzC,sBAAsB,CAAC,EAAE,SAAS,CAAC;IACnC,mBAAmB,CAAC,EAAE,SAAS,CAAC;IAChC,iBAAiB,CAAC,EAAE,SAAS,CAAC;IAC9B,sBAAsB,CAAC,EAAE,SAAS,CAAC;IACnC,oBAAoB,CAAC,EAAE,SAAS,CAAC;IACjC,qBAAqB,CAAC,EAAE,oBAAoB,CAAC,4BAA4B,CAAC,CAAC;CAC5E;AAuCD,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,kBAAkB;IACjC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,iBAAiB,CAAC,EAAE,gBAAgB,EAAE,CAAC;IACvC,UAAU,CAAC,EAAE,mBAAmB,EAAE,CAAC;IACnC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,WAAW,CAAC,EAAE,UAAU,EAAE,CAAC;CAC5B;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CA2ChD;AAiCD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,GAAG,kBAAkB,CAgG/D"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filter custom lint rules by per-rule `include` / `exclude` glob patterns.
|
|
3
|
+
*
|
|
4
|
+
* Patterns are matched against a path relative to the config file's directory.
|
|
5
|
+
* Path separators are normalized to forward slashes so cross-platform patterns
|
|
6
|
+
* like `questions/**` work regardless of host OS.
|
|
7
|
+
*
|
|
8
|
+
* Uses Node's `path.matchesGlob` (available since Node 22.5), so this module
|
|
9
|
+
* is Node-only — the browser entrypoint does not import it.
|
|
10
|
+
*/
|
|
11
|
+
import type { CustomRule } from './configSchema.js';
|
|
12
|
+
export declare function ruleMatchesPath(rule: CustomRule, relPath: string): boolean;
|
|
13
|
+
export declare function filterCustomRulesForPath(rules: CustomRule[] | undefined, relPath: string): CustomRule[] | undefined;
|
|
14
|
+
//# sourceMappingURL=customRuleFilter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"customRuleFilter.d.ts","sourceRoot":"","sources":["../../../src/core/customRuleFilter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAEpD,wBAAgB,eAAe,CAAC,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAS1E;AAED,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,UAAU,EAAE,GAAG,SAAS,EAC/B,OAAO,EAAE,MAAM,GACd,UAAU,EAAE,GAAG,SAAS,CAG1B"}
|
package/cli/out/main.js
CHANGED
|
@@ -784,6 +784,14 @@ function validateConfig(raw) {
|
|
|
784
784
|
if (typeof e2.severity === "string" && VALID_RULE_SEVERITIES.has(e2.severity)) {
|
|
785
785
|
rule.severity = e2.severity;
|
|
786
786
|
}
|
|
787
|
+
if (Array.isArray(e2.include)) {
|
|
788
|
+
const items = e2.include.filter((s2) => typeof s2 === "string" && s2.length > 0);
|
|
789
|
+
if (items.length > 0) rule.include = items;
|
|
790
|
+
}
|
|
791
|
+
if (Array.isArray(e2.exclude)) {
|
|
792
|
+
const items = e2.exclude.filter((s2) => typeof s2 === "string" && s2.length > 0);
|
|
793
|
+
if (items.length > 0) rule.exclude = items;
|
|
794
|
+
}
|
|
787
795
|
rules.push(rule);
|
|
788
796
|
}
|
|
789
797
|
if (rules.length > 0) config.customRules = rules;
|
|
@@ -815,7 +823,8 @@ function loadConfigFileForPath(filePath) {
|
|
|
815
823
|
try {
|
|
816
824
|
const text2 = fs.readFileSync(configPath, "utf-8");
|
|
817
825
|
const raw = parseJsonc(text2);
|
|
818
|
-
|
|
826
|
+
const config = validateConfig(raw);
|
|
827
|
+
return { config, configDir: path2.dirname(configPath) };
|
|
819
828
|
} catch {
|
|
820
829
|
return null;
|
|
821
830
|
}
|
|
@@ -1916,9 +1925,9 @@ function preprocessMustacheLiterals(raw) {
|
|
|
1916
1925
|
case "=":
|
|
1917
1926
|
return null;
|
|
1918
1927
|
default: {
|
|
1919
|
-
const
|
|
1920
|
-
if (
|
|
1921
|
-
out += `:m-variable(${
|
|
1928
|
+
const path6 = body.trim();
|
|
1929
|
+
if (path6.length === 0) return null;
|
|
1930
|
+
out += `:m-variable(${path6})`;
|
|
1922
1931
|
break;
|
|
1923
1932
|
}
|
|
1924
1933
|
}
|
|
@@ -2669,6 +2678,23 @@ function collectErrors(tree, rules, customTagNames, customRules) {
|
|
|
2669
2678
|
);
|
|
2670
2679
|
}
|
|
2671
2680
|
|
|
2681
|
+
// src/core/customRuleFilter.ts
|
|
2682
|
+
var path3 = __toESM(require("node:path"));
|
|
2683
|
+
function ruleMatchesPath(rule, relPath) {
|
|
2684
|
+
const normalized = relPath.split(path3.sep).join("/");
|
|
2685
|
+
if (rule.exclude && rule.exclude.some((p2) => path3.matchesGlob(normalized, p2))) {
|
|
2686
|
+
return false;
|
|
2687
|
+
}
|
|
2688
|
+
if (rule.include && rule.include.length > 0) {
|
|
2689
|
+
return rule.include.some((p2) => path3.matchesGlob(normalized, p2));
|
|
2690
|
+
}
|
|
2691
|
+
return true;
|
|
2692
|
+
}
|
|
2693
|
+
function filterCustomRulesForPath(rules, relPath) {
|
|
2694
|
+
if (!rules) return rules;
|
|
2695
|
+
return rules.filter((r2) => ruleMatchesPath(r2, relPath));
|
|
2696
|
+
}
|
|
2697
|
+
|
|
2672
2698
|
// src/core/diagnostic.ts
|
|
2673
2699
|
function toFix(r2) {
|
|
2674
2700
|
return { range: [r2.startIndex, r2.endIndex], newText: r2.newText };
|
|
@@ -2768,6 +2794,7 @@ var DEFAULT_EXCLUDE_SEGMENTS = ["/node_modules/", "/.git/"];
|
|
|
2768
2794
|
function resolveFiles(cliPatterns) {
|
|
2769
2795
|
const configPath = findConfigFile(process.cwd());
|
|
2770
2796
|
let config = null;
|
|
2797
|
+
const configDir = configPath ? import_node_path.default.dirname(configPath) : null;
|
|
2771
2798
|
if (configPath) {
|
|
2772
2799
|
try {
|
|
2773
2800
|
const text2 = import_node_fs.default.readFileSync(configPath, "utf-8");
|
|
@@ -2783,7 +2810,7 @@ function resolveFiles(cliPatterns) {
|
|
|
2783
2810
|
} else if (config?.include && config.include.length > 0) {
|
|
2784
2811
|
patterns = config.include;
|
|
2785
2812
|
} else {
|
|
2786
|
-
return { files: [], config };
|
|
2813
|
+
return { files: [], config, configDir };
|
|
2787
2814
|
}
|
|
2788
2815
|
let files = expandGlobs(patterns);
|
|
2789
2816
|
files = files.filter((f2) => !DEFAULT_EXCLUDE_SEGMENTS.some((seg) => f2.includes(seg)));
|
|
@@ -2801,7 +2828,7 @@ function resolveFiles(cliPatterns) {
|
|
|
2801
2828
|
}
|
|
2802
2829
|
files = files.filter((f2) => !excludeSet.has(f2));
|
|
2803
2830
|
}
|
|
2804
|
-
return { files, config };
|
|
2831
|
+
return { files, config, configDir };
|
|
2805
2832
|
}
|
|
2806
2833
|
function applyFixes(source, errors) {
|
|
2807
2834
|
const replacements = [];
|
|
@@ -2848,7 +2875,7 @@ async function run(args) {
|
|
|
2848
2875
|
}
|
|
2849
2876
|
const fixMode = args.includes("--fix");
|
|
2850
2877
|
const patterns = args.filter((a2) => a2 !== "--fix");
|
|
2851
|
-
const { files, config } = resolveFiles(patterns);
|
|
2878
|
+
const { files, config, configDir } = resolveFiles(patterns);
|
|
2852
2879
|
if (files.length === 0) {
|
|
2853
2880
|
if (patterns.length === 0 && (!config?.include || config.include.length === 0)) {
|
|
2854
2881
|
console.log(USAGE);
|
|
@@ -2870,12 +2897,15 @@ async function run(args) {
|
|
|
2870
2897
|
const rules = config?.rules;
|
|
2871
2898
|
const customTagNames = config?.customTags?.map((t2) => t2.name);
|
|
2872
2899
|
const customRules = config?.customRules;
|
|
2900
|
+
const ruleFilterBase = configDir ?? cwd;
|
|
2873
2901
|
for (const file of files) {
|
|
2874
2902
|
const displayPath = import_node_path.default.relative(cwd, file) || file;
|
|
2903
|
+
const ruleFilterPath = import_node_path.default.relative(ruleFilterBase, file) || displayPath;
|
|
2904
|
+
const applicableCustomRules = filterCustomRulesForPath(customRules, ruleFilterPath);
|
|
2875
2905
|
let source = import_node_fs.default.readFileSync(file, "utf-8");
|
|
2876
2906
|
if (fixMode) {
|
|
2877
2907
|
const tree2 = parseDocument(source);
|
|
2878
|
-
const errors2 = collectErrors2(tree2, displayPath, rules, customTagNames,
|
|
2908
|
+
const errors2 = collectErrors2(tree2, displayPath, rules, customTagNames, applicableCustomRules);
|
|
2879
2909
|
const fixed = applyFixes(source, errors2);
|
|
2880
2910
|
if (fixed !== source) {
|
|
2881
2911
|
import_node_fs.default.writeFileSync(file, fixed, "utf-8");
|
|
@@ -2883,7 +2913,7 @@ async function run(args) {
|
|
|
2883
2913
|
}
|
|
2884
2914
|
}
|
|
2885
2915
|
const tree = parseDocument(source);
|
|
2886
|
-
const errors = collectErrors2(tree, displayPath, rules, customTagNames,
|
|
2916
|
+
const errors = collectErrors2(tree, displayPath, rules, customTagNames, applicableCustomRules);
|
|
2887
2917
|
const fileErrors = errors.filter((e2) => e2.severity !== "warning");
|
|
2888
2918
|
const fileWarnings = errors.filter((e2) => e2.severity === "warning");
|
|
2889
2919
|
if (errors.length > 0) {
|
package/package.json
CHANGED
|
@@ -107,6 +107,20 @@ describe('lint', () => {
|
|
|
107
107
|
expect(d.some((x) => x.ruleName === 'no-script')).toBe(true);
|
|
108
108
|
});
|
|
109
109
|
|
|
110
|
+
it('rejects per-rule include/exclude at the type level', () => {
|
|
111
|
+
linter.lint('<script>x</script>', {
|
|
112
|
+
customRules: [{
|
|
113
|
+
id: 'no-script',
|
|
114
|
+
selector: 'script',
|
|
115
|
+
message: 'Bare <script> is disallowed',
|
|
116
|
+
// @ts-expect-error include is stripped from the browser CustomRule type
|
|
117
|
+
include: ['questions/**'],
|
|
118
|
+
// @ts-expect-error exclude is stripped from the browser CustomRule type
|
|
119
|
+
exclude: ['**/legacy/**'],
|
|
120
|
+
}],
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
110
124
|
it('honors <!-- htmlmustache-disable ruleName --> directives', () => {
|
|
111
125
|
const src = '<!-- htmlmustache-disable duplicateAttributes -->\n<p id="a" id="b"></p>';
|
|
112
126
|
const d = linter.lint(src, DEFAULT_CONFIG);
|
package/src/browser/index.ts
CHANGED
|
@@ -28,8 +28,17 @@ import type {
|
|
|
28
28
|
} from '../core/configSchema.js';
|
|
29
29
|
import type { CustomCodeTagConfig } from '../core/customCodeTags.js';
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
/**
|
|
32
|
+
* `include`/`exclude` on custom rules are stripped from the browser surface:
|
|
33
|
+
* the browser API has no filesystem path to match against, so those fields
|
|
34
|
+
* would silently do nothing. Users who share a `.htmlmustache.jsonc` config
|
|
35
|
+
* between the CLI and a web playground should strip them before passing, or
|
|
36
|
+
* let TypeScript catch the mismatch.
|
|
37
|
+
*/
|
|
38
|
+
export type CustomRule = Omit<CustomRuleType, 'include' | 'exclude'>;
|
|
39
|
+
export type Config =
|
|
40
|
+
Omit<HtmlMustacheConfig, 'include' | 'exclude' | 'customRules'>
|
|
41
|
+
& { customRules?: CustomRule[] };
|
|
33
42
|
export type CustomTag = CustomCodeTagConfig;
|
|
34
43
|
export type { RulesConfig, RuleSeverity, PrettierLike, Diagnostic };
|
|
35
44
|
|
package/src/core/configSchema.ts
CHANGED
|
@@ -79,6 +79,14 @@ export interface CustomRule {
|
|
|
79
79
|
selector: string;
|
|
80
80
|
message: string;
|
|
81
81
|
severity?: RuleSeverity;
|
|
82
|
+
/**
|
|
83
|
+
* Optional glob patterns (relative to the config file) restricting which
|
|
84
|
+
* files this rule applies to. Applied as an additional filter on top of the
|
|
85
|
+
* top-level `include`/`exclude` — a rule only fires for files that both
|
|
86
|
+
* the top-level settings include AND the per-rule settings include.
|
|
87
|
+
*/
|
|
88
|
+
include?: string[];
|
|
89
|
+
exclude?: string[];
|
|
82
90
|
}
|
|
83
91
|
|
|
84
92
|
export interface NoBreakDelimiter {
|
|
@@ -264,6 +272,14 @@ export function validateConfig(raw: unknown): HtmlMustacheConfig {
|
|
|
264
272
|
if (typeof e.severity === 'string' && VALID_RULE_SEVERITIES.has(e.severity)) {
|
|
265
273
|
rule.severity = e.severity as RuleSeverity;
|
|
266
274
|
}
|
|
275
|
+
if (Array.isArray(e.include)) {
|
|
276
|
+
const items = e.include.filter((s: unknown) => typeof s === 'string' && s.length > 0);
|
|
277
|
+
if (items.length > 0) rule.include = items as string[];
|
|
278
|
+
}
|
|
279
|
+
if (Array.isArray(e.exclude)) {
|
|
280
|
+
const items = e.exclude.filter((s: unknown) => typeof s === 'string' && s.length > 0);
|
|
281
|
+
if (items.length > 0) rule.exclude = items as string[];
|
|
282
|
+
}
|
|
267
283
|
rules.push(rule);
|
|
268
284
|
}
|
|
269
285
|
if (rules.length > 0) config.customRules = rules;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filter custom lint rules by per-rule `include` / `exclude` glob patterns.
|
|
3
|
+
*
|
|
4
|
+
* Patterns are matched against a path relative to the config file's directory.
|
|
5
|
+
* Path separators are normalized to forward slashes so cross-platform patterns
|
|
6
|
+
* like `questions/**` work regardless of host OS.
|
|
7
|
+
*
|
|
8
|
+
* Uses Node's `path.matchesGlob` (available since Node 22.5), so this module
|
|
9
|
+
* is Node-only — the browser entrypoint does not import it.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import * as path from 'node:path';
|
|
13
|
+
import type { CustomRule } from './configSchema.js';
|
|
14
|
+
|
|
15
|
+
export function ruleMatchesPath(rule: CustomRule, relPath: string): boolean {
|
|
16
|
+
const normalized = relPath.split(path.sep).join('/');
|
|
17
|
+
if (rule.exclude && rule.exclude.some(p => path.matchesGlob(normalized, p))) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
if (rule.include && rule.include.length > 0) {
|
|
21
|
+
return rule.include.some(p => path.matchesGlob(normalized, p));
|
|
22
|
+
}
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function filterCustomRulesForPath(
|
|
27
|
+
rules: CustomRule[] | undefined,
|
|
28
|
+
relPath: string,
|
|
29
|
+
): CustomRule[] | undefined {
|
|
30
|
+
if (!rules) return rules;
|
|
31
|
+
return rules.filter(r => ruleMatchesPath(r, relPath));
|
|
32
|
+
}
|