@reteps/tree-sitter-htmlmustache 0.9.0 → 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 +201 -67
- package/browser/out/browser/index.mjs.map +3 -3
- 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/browser/out/core/selectorMatcher.d.ts +19 -6
- package/browser/out/core/selectorMatcher.d.ts.map +1 -1
- package/cli/out/main.js +239 -75
- 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
- package/src/core/selectorMatcher.ts +272 -72
|
@@ -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"}
|
|
@@ -10,14 +10,21 @@
|
|
|
10
10
|
* Supported user-facing syntax:
|
|
11
11
|
* - Tag names (`div`), universal (`*`), classes (`.foo`), ids (`#foo`)
|
|
12
12
|
* - Attributes: `[attr]`, `[attr=v]`, `[attr^=v]`, `[attr*=v]`, `[attr$=v]`, `[attr~=v]`
|
|
13
|
-
* - Descendant (space)
|
|
13
|
+
* - Descendant (space), child (`>`), adjacent-sibling (`+`), and
|
|
14
|
+
* general-sibling (`~`) combinators. Sibling combinators skip over text /
|
|
15
|
+
* whitespace nodes (CSS semantics) and work across HTML and Mustache
|
|
16
|
+
* constructs: e.g. `label + input`, `{{foo}} + p`, `h2 ~ {{#items}}`.
|
|
14
17
|
* - Mustache variables: `{{path}}` and `{{{path}}}` (raw)
|
|
15
18
|
* - Mustache sections: `{{#name}}` and `{{^name}}` (inverted)
|
|
16
19
|
* - Mustache comments: `{{!content}}`
|
|
17
20
|
* - Mustache partials: `{{>name}}`
|
|
18
21
|
* - Glob wildcard `*` inside the argument: `{{options.*}}`, `{{*.deprecated}}`, `{{*}}`
|
|
19
22
|
* - `:has(selector)` — element has a matching descendant
|
|
20
|
-
* - `:not(...)`
|
|
23
|
+
* - `:not(...)` — negation. Accepts attributes/class/id/`:has` (folded into
|
|
24
|
+
* the outer compound), plus any other selector (including Mustache
|
|
25
|
+
* literals and type selectors) as a whole-selector check against the
|
|
26
|
+
* node itself. Example: `{{*}}:not({{internal.*}})` matches any
|
|
27
|
+
* interpolation whose path does not start with `internal.`.
|
|
21
28
|
* - `:root` — the tree-sitter fragment root (the whole document). Unlike
|
|
22
29
|
* browser CSS where `:root` matches `<html>`, this matches the parse-tree
|
|
23
30
|
* root so it works on partials/fragments too. Useful as a document-scoped
|
|
@@ -26,15 +33,19 @@
|
|
|
26
33
|
* `pl-answer-panel` is. Cannot combine with tag/class/id/attribute in the
|
|
27
34
|
* same compound (only with `:has` / `:not(:has(...))`). Inside `:has(...)`,
|
|
28
35
|
* `:root` refers to the element being checked, not the document.
|
|
36
|
+
* - `:is(a, b, ...)` — matches if any alternative matches. Expanded at parse
|
|
37
|
+
* time into the Cartesian product of alternatives, so `:is(a, b) :is(c, d)`
|
|
38
|
+
* is equivalent to `a c, a d, b c, b d`. Alternatives inside `:is` that
|
|
39
|
+
* contain combinators are only allowed when the `:is(...)` stands alone in
|
|
40
|
+
* its compound (e.g. `:is(div > span, p)` works; `x:is(div > span, p)`
|
|
41
|
+
* does not, since a combinator can't be merged into another compound).
|
|
29
42
|
* - Comma-separated alternatives
|
|
30
43
|
*
|
|
31
44
|
* Unsupported (parseSelector returns null, rule is skipped):
|
|
32
|
-
* - Sibling combinators (`+`, `~`)
|
|
33
45
|
* - `[attr|=v]`, case-insensitive `i` flag
|
|
34
46
|
* - Mixed HTML + Mustache kinds in one compound (e.g. `img{{foo}}`)
|
|
35
47
|
* - `{{/end}}` (end tags aren't standalone nodes)
|
|
36
48
|
* - `{{=<% %>=}}` (delimiter changes aren't grammar-tracked)
|
|
37
|
-
* - Mustache literals inside `:not(...)` (only attribute/class/id/:has)
|
|
38
49
|
*/
|
|
39
50
|
import type { BalanceNode } from './htmlBalanceChecker.js';
|
|
40
51
|
export type AttributeOperator = '=' | '^=' | '*=' | '$=' | '~=';
|
|
@@ -49,6 +60,7 @@ export interface DescendantCheck {
|
|
|
49
60
|
selector: ParsedSelector;
|
|
50
61
|
negated: boolean;
|
|
51
62
|
}
|
|
63
|
+
export type Combinator = 'descendant' | 'child' | 'adjacent-sibling' | 'general-sibling';
|
|
52
64
|
export interface Segment {
|
|
53
65
|
kind: SegmentKind;
|
|
54
66
|
rootOnly: boolean;
|
|
@@ -56,7 +68,8 @@ export interface Segment {
|
|
|
56
68
|
pathRegex?: RegExp;
|
|
57
69
|
attributes: AttributeConstraint[];
|
|
58
70
|
descendantChecks: DescendantCheck[];
|
|
59
|
-
|
|
71
|
+
selfNegations: ParsedSelector[];
|
|
72
|
+
combinator: Combinator;
|
|
60
73
|
}
|
|
61
74
|
/** A parsed selector is a list of alternatives (from comma-separated parts). */
|
|
62
75
|
export type ParsedSelector = Segment[][];
|
|
@@ -70,5 +83,5 @@ export type ParsedSelector = Segment[][];
|
|
|
70
83
|
*/
|
|
71
84
|
export declare function preprocessMustacheLiterals(raw: string): string | null;
|
|
72
85
|
export declare function parseSelector(raw: string): ParsedSelector | null;
|
|
73
|
-
export declare function matchSelector(rootNode: BalanceNode, selector: ParsedSelector): BalanceNode[];
|
|
86
|
+
export declare function matchSelector(rootNode: BalanceNode, selector: ParsedSelector, siblings?: BalanceNode[], indexInSiblings?: number): BalanceNode[];
|
|
74
87
|
//# sourceMappingURL=selectorMatcher.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"selectorMatcher.d.ts","sourceRoot":"","sources":["../../../src/core/selectorMatcher.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"selectorMatcher.d.ts","sourceRoot":"","sources":["../../../src/core/selectorMatcher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAY3D,MAAM,MAAM,iBAAiB,GAAG,GAAG,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAEhE,MAAM,MAAM,WAAW,GACnB,MAAM,GACN,SAAS,GACT,UAAU,GACV,UAAU,GACV,KAAK,GACL,SAAS,GACT,SAAS,CAAC;AAEd,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,iBAAiB,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,cAAc,CAAC;IACzB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,MAAM,UAAU,GAAG,YAAY,GAAG,OAAO,GAAG,kBAAkB,GAAG,iBAAiB,CAAC;AAEzF,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,WAAW,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,mBAAmB,EAAE,CAAC;IAClC,gBAAgB,EAAE,eAAe,EAAE,CAAC;IACpC,aAAa,EAAE,cAAc,EAAE,CAAC;IAChC,UAAU,EAAE,UAAU,CAAC;CACxB;AAED,gFAAgF;AAChF,MAAM,MAAM,cAAc,GAAG,OAAO,EAAE,EAAE,CAAC;AAQzC;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAuFrE;AAID,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CA2BhE;AA2pBD,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,WAAW,EACrB,QAAQ,EAAE,cAAc,EACxB,QAAQ,GAAE,WAAW,EAAO,EAC5B,eAAe,SAAI,GAClB,WAAW,EAAE,CAYf"}
|
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
|
}
|
|
@@ -1939,13 +1948,85 @@ function parseSelector(raw) {
|
|
|
1939
1948
|
const tops = ast.type === "list" ? ast.list : [ast];
|
|
1940
1949
|
const alts = [];
|
|
1941
1950
|
for (const top of tops) {
|
|
1942
|
-
const
|
|
1943
|
-
if (
|
|
1944
|
-
|
|
1945
|
-
|
|
1951
|
+
const expanded = expandIs(top);
|
|
1952
|
+
if (expanded === null) return null;
|
|
1953
|
+
for (const exp of expanded) {
|
|
1954
|
+
const segments = [];
|
|
1955
|
+
if (!collectSegments(exp, "descendant", segments)) return null;
|
|
1956
|
+
if (segments.length === 0) return null;
|
|
1957
|
+
alts.push(segments);
|
|
1958
|
+
}
|
|
1946
1959
|
}
|
|
1947
1960
|
return alts.length > 0 ? alts : null;
|
|
1948
1961
|
}
|
|
1962
|
+
function expandIs(ast) {
|
|
1963
|
+
switch (ast.type) {
|
|
1964
|
+
case "list": {
|
|
1965
|
+
const out = [];
|
|
1966
|
+
for (const alt of ast.list) {
|
|
1967
|
+
const expanded = expandIs(alt);
|
|
1968
|
+
if (expanded === null) return null;
|
|
1969
|
+
out.push(...expanded);
|
|
1970
|
+
}
|
|
1971
|
+
return out;
|
|
1972
|
+
}
|
|
1973
|
+
case "complex": {
|
|
1974
|
+
const lefts = expandIs(ast.left);
|
|
1975
|
+
if (lefts === null) return null;
|
|
1976
|
+
const rights = expandIs(ast.right);
|
|
1977
|
+
if (rights === null) return null;
|
|
1978
|
+
const out = [];
|
|
1979
|
+
for (const l2 of lefts) for (const r2 of rights) {
|
|
1980
|
+
out.push({ ...ast, left: l2, right: r2 });
|
|
1981
|
+
}
|
|
1982
|
+
return out;
|
|
1983
|
+
}
|
|
1984
|
+
case "compound": {
|
|
1985
|
+
if (ast.list.length === 1) {
|
|
1986
|
+
const tok = ast.list[0];
|
|
1987
|
+
if (tok.type === "pseudo-class" && tok.name === "is") {
|
|
1988
|
+
if (!tok.subtree) return null;
|
|
1989
|
+
return expandIs(tok.subtree);
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
return expandCompoundWithIs(ast.list);
|
|
1993
|
+
}
|
|
1994
|
+
default:
|
|
1995
|
+
if (ast.type === "pseudo-class" && ast.name === "is") {
|
|
1996
|
+
if (!ast.subtree) return null;
|
|
1997
|
+
return expandIs(ast.subtree);
|
|
1998
|
+
}
|
|
1999
|
+
return [ast];
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
function expandCompoundWithIs(tokens) {
|
|
2003
|
+
let variants = [[]];
|
|
2004
|
+
for (const tok of tokens) {
|
|
2005
|
+
if (tok.type === "pseudo-class" && tok.name === "is") {
|
|
2006
|
+
if (!tok.subtree) return null;
|
|
2007
|
+
const alts = expandIs(tok.subtree);
|
|
2008
|
+
if (alts === null) return null;
|
|
2009
|
+
const next = [];
|
|
2010
|
+
for (const base of variants) {
|
|
2011
|
+
for (const alt of alts) {
|
|
2012
|
+
if (alt.type === "compound") {
|
|
2013
|
+
next.push([...base, ...alt.list]);
|
|
2014
|
+
} else if (alt.type === "complex" || alt.type === "list" || alt.type === "relative") {
|
|
2015
|
+
return null;
|
|
2016
|
+
} else {
|
|
2017
|
+
next.push([...base, alt]);
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
variants = next;
|
|
2022
|
+
} else {
|
|
2023
|
+
variants = variants.map((v) => [...v, tok]);
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
return variants.map(
|
|
2027
|
+
(list) => list.length === 1 ? list[0] : { type: "compound", list }
|
|
2028
|
+
);
|
|
2029
|
+
}
|
|
1949
2030
|
function collectSegments(ast, combinator, out) {
|
|
1950
2031
|
if (ast.type === "complex") {
|
|
1951
2032
|
const mapped = mapCombinator(ast.combinator);
|
|
@@ -1963,6 +2044,8 @@ function mapCombinator(c2) {
|
|
|
1963
2044
|
const trimmed = c2.trim();
|
|
1964
2045
|
if (trimmed === "") return "descendant";
|
|
1965
2046
|
if (trimmed === ">") return "child";
|
|
2047
|
+
if (trimmed === "+") return "adjacent-sibling";
|
|
2048
|
+
if (trimmed === "~") return "general-sibling";
|
|
1966
2049
|
return null;
|
|
1967
2050
|
}
|
|
1968
2051
|
function segmentFromCompound(ast) {
|
|
@@ -1973,6 +2056,7 @@ function segmentFromCompound(ast) {
|
|
|
1973
2056
|
let rootOnly = false;
|
|
1974
2057
|
const attributes = [];
|
|
1975
2058
|
const descendantChecks = [];
|
|
2059
|
+
const selfNegations = [];
|
|
1976
2060
|
const forbidChange = (requested) => {
|
|
1977
2061
|
if (kind === void 0) return false;
|
|
1978
2062
|
if (kind === requested) return false;
|
|
@@ -2026,7 +2110,7 @@ function segmentFromCompound(ast) {
|
|
|
2026
2110
|
break;
|
|
2027
2111
|
}
|
|
2028
2112
|
if (token.name === "not") {
|
|
2029
|
-
if (!applyNegatedSubtree(token.subtree, attributes, descendantChecks)) return null;
|
|
2113
|
+
if (!applyNegatedSubtree(token.subtree, attributes, descendantChecks, selfNegations)) return null;
|
|
2030
2114
|
break;
|
|
2031
2115
|
}
|
|
2032
2116
|
if (token.name === "root") {
|
|
@@ -2048,7 +2132,7 @@ function segmentFromCompound(ast) {
|
|
|
2048
2132
|
}
|
|
2049
2133
|
const isHtml = kind === "html";
|
|
2050
2134
|
const finalAttrs = isHtml ? attributes : [];
|
|
2051
|
-
return { kind, rootOnly, name, pathRegex, attributes: finalAttrs, descendantChecks, combinator: "descendant" };
|
|
2135
|
+
return { kind, rootOnly, name, pathRegex, attributes: finalAttrs, descendantChecks, selfNegations, combinator: "descendant" };
|
|
2052
2136
|
}
|
|
2053
2137
|
function mustacheKindFromMarker(name) {
|
|
2054
2138
|
switch (name) {
|
|
@@ -2113,7 +2197,7 @@ function classConstraint(token, negated) {
|
|
|
2113
2197
|
function idConstraint(token, negated) {
|
|
2114
2198
|
return { name: "id", op: "=", value: token.name, negated };
|
|
2115
2199
|
}
|
|
2116
|
-
function applyNegatedSubtree(subtree, attributes, descendantChecks) {
|
|
2200
|
+
function applyNegatedSubtree(subtree, attributes, descendantChecks, selfNegations) {
|
|
2117
2201
|
if (!subtree) return false;
|
|
2118
2202
|
if (subtree.type === "attribute") {
|
|
2119
2203
|
const c2 = attributeConstraint(subtree, true);
|
|
@@ -2130,9 +2214,14 @@ function applyNegatedSubtree(subtree, attributes, descendantChecks) {
|
|
|
2130
2214
|
return true;
|
|
2131
2215
|
}
|
|
2132
2216
|
if (subtree.type === "pseudo-class" && subtree.name === "has") {
|
|
2133
|
-
const
|
|
2134
|
-
if (!
|
|
2135
|
-
descendantChecks.push({ selector:
|
|
2217
|
+
const sel2 = subtreeToSelector(subtree.subtree);
|
|
2218
|
+
if (!sel2) return false;
|
|
2219
|
+
descendantChecks.push({ selector: sel2, negated: true });
|
|
2220
|
+
return true;
|
|
2221
|
+
}
|
|
2222
|
+
const sel = subtreeToSelector(subtree);
|
|
2223
|
+
if (sel) {
|
|
2224
|
+
selfNegations.push(sel);
|
|
2136
2225
|
return true;
|
|
2137
2226
|
}
|
|
2138
2227
|
return false;
|
|
@@ -2232,11 +2321,20 @@ function checkDescendants(node, checks) {
|
|
|
2232
2321
|
return true;
|
|
2233
2322
|
}
|
|
2234
2323
|
function hasDescendantMatch(node, selector) {
|
|
2235
|
-
for (
|
|
2236
|
-
if (matchSelector(
|
|
2324
|
+
for (let i2 = 0; i2 < node.children.length; i2++) {
|
|
2325
|
+
if (matchSelector(node.children[i2], selector, node.children, i2).length > 0) return true;
|
|
2237
2326
|
}
|
|
2238
2327
|
return false;
|
|
2239
2328
|
}
|
|
2329
|
+
function checkSelfNegations(node, negations, rootNode) {
|
|
2330
|
+
for (const sel of negations) {
|
|
2331
|
+
for (const alt of sel) {
|
|
2332
|
+
if (alt.length !== 1) continue;
|
|
2333
|
+
if (nodeMatchesSegment(node, alt[0], rootNode)) return false;
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
2336
|
+
return true;
|
|
2337
|
+
}
|
|
2240
2338
|
function matchesName(actual, segment) {
|
|
2241
2339
|
if (segment.name === null) return true;
|
|
2242
2340
|
if (actual === null) return false;
|
|
@@ -2246,51 +2344,73 @@ function matchesName(actual, segment) {
|
|
|
2246
2344
|
function nodeMatchesSegment(node, segment, rootNode) {
|
|
2247
2345
|
if (segment.rootOnly) {
|
|
2248
2346
|
if (node !== rootNode) return false;
|
|
2249
|
-
return checkDescendants(node, segment.descendantChecks);
|
|
2250
|
-
}
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2347
|
+
return checkDescendants(node, segment.descendantChecks) && checkSelfNegations(node, segment.selfNegations, rootNode);
|
|
2348
|
+
}
|
|
2349
|
+
const baseMatches = (() => {
|
|
2350
|
+
switch (segment.kind) {
|
|
2351
|
+
case "html": {
|
|
2352
|
+
if (!HTML_ELEMENT_TYPES.has(node.type)) return false;
|
|
2353
|
+
if (segment.name !== null) {
|
|
2354
|
+
const tagName = getTagName(node)?.toLowerCase();
|
|
2355
|
+
if (tagName !== segment.name) return false;
|
|
2356
|
+
}
|
|
2357
|
+
return checkAttributes(node, segment.attributes) && checkDescendants(node, segment.descendantChecks);
|
|
2257
2358
|
}
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2359
|
+
case "section":
|
|
2360
|
+
if (node.type !== "mustache_section") return false;
|
|
2361
|
+
if (!matchesName(getSectionName(node)?.toLowerCase() ?? null, segment)) return false;
|
|
2362
|
+
return checkDescendants(node, segment.descendantChecks);
|
|
2363
|
+
case "inverted":
|
|
2364
|
+
if (node.type !== "mustache_inverted_section") return false;
|
|
2365
|
+
if (!matchesName(getSectionName(node)?.toLowerCase() ?? null, segment)) return false;
|
|
2366
|
+
return checkDescendants(node, segment.descendantChecks);
|
|
2367
|
+
case "variable":
|
|
2368
|
+
if (node.type !== "mustache_interpolation") return false;
|
|
2369
|
+
if (!matchesName(getInterpolationPath(node)?.toLowerCase() ?? null, segment)) return false;
|
|
2370
|
+
return checkDescendants(node, segment.descendantChecks);
|
|
2371
|
+
case "raw":
|
|
2372
|
+
if (node.type !== "mustache_triple") return false;
|
|
2373
|
+
if (!matchesName(getInterpolationPath(node)?.toLowerCase() ?? null, segment)) return false;
|
|
2374
|
+
return checkDescendants(node, segment.descendantChecks);
|
|
2375
|
+
case "comment":
|
|
2376
|
+
if (node.type !== "mustache_comment") return false;
|
|
2377
|
+
if (!matchesName(getCommentContent(node)?.toLowerCase() ?? null, segment)) return false;
|
|
2378
|
+
return checkDescendants(node, segment.descendantChecks);
|
|
2379
|
+
case "partial":
|
|
2380
|
+
if (node.type !== "mustache_partial") return false;
|
|
2381
|
+
if (!matchesName(getPartialName(node)?.toLowerCase() ?? null, segment)) return false;
|
|
2382
|
+
return checkDescendants(node, segment.descendantChecks);
|
|
2383
|
+
}
|
|
2384
|
+
})();
|
|
2385
|
+
if (!baseMatches) return false;
|
|
2386
|
+
return checkSelfNegations(node, segment.selfNegations, rootNode);
|
|
2387
|
+
}
|
|
2388
|
+
function checkPrefix(cursor, segments, segIdx, stepCombinator, rootNode) {
|
|
2287
2389
|
if (segIdx < 0) return true;
|
|
2288
2390
|
const segment = segments[segIdx];
|
|
2391
|
+
if (stepCombinator === "adjacent-sibling" || stepCombinator === "general-sibling") {
|
|
2392
|
+
for (let i2 = cursor.indexInSiblings - 1; i2 >= 0; i2--) {
|
|
2393
|
+
const sib = cursor.siblings[i2];
|
|
2394
|
+
if (!isMatchableNode(sib)) continue;
|
|
2395
|
+
if (!nodeMatchesSegment(sib, segment, rootNode)) {
|
|
2396
|
+
if (stepCombinator === "adjacent-sibling") return false;
|
|
2397
|
+
continue;
|
|
2398
|
+
}
|
|
2399
|
+
const newCursor = {
|
|
2400
|
+
ancestors: cursor.ancestors,
|
|
2401
|
+
siblings: cursor.siblings,
|
|
2402
|
+
indexInSiblings: i2
|
|
2403
|
+
};
|
|
2404
|
+
if (checkPrefix(newCursor, segments, segIdx - 1, segment.combinator, rootNode)) return true;
|
|
2405
|
+
if (stepCombinator === "adjacent-sibling") return false;
|
|
2406
|
+
}
|
|
2407
|
+
return false;
|
|
2408
|
+
}
|
|
2289
2409
|
const ancestorKind = ancestorKindForSegment(segment);
|
|
2290
2410
|
if (ancestorKind === null) return false;
|
|
2291
|
-
if (
|
|
2292
|
-
for (let a2 = ancestors.length - 1; a2 >= 0; a2--) {
|
|
2293
|
-
const entry = ancestors[a2];
|
|
2411
|
+
if (stepCombinator === "child") {
|
|
2412
|
+
for (let a2 = cursor.ancestors.length - 1; a2 >= 0; a2--) {
|
|
2413
|
+
const entry = cursor.ancestors[a2];
|
|
2294
2414
|
if (entry.kind !== ancestorKind) {
|
|
2295
2415
|
if (ancestorKind === "root" && entry.kind === "html") return false;
|
|
2296
2416
|
continue;
|
|
@@ -2298,22 +2418,35 @@ function checkAncestors(ancestors, segments, segIdx, childCombinator) {
|
|
|
2298
2418
|
if (!matchesName(entry.name, segment)) return false;
|
|
2299
2419
|
if (segment.kind === "html" && !checkAttributes(entry.node, segment.attributes)) return false;
|
|
2300
2420
|
if (!checkDescendants(entry.node, segment.descendantChecks)) return false;
|
|
2301
|
-
|
|
2421
|
+
if (!checkSelfNegations(entry.node, segment.selfNegations, rootNode)) return false;
|
|
2422
|
+
const newCursor = {
|
|
2423
|
+
ancestors: cursor.ancestors.slice(0, a2),
|
|
2424
|
+
siblings: entry.siblings,
|
|
2425
|
+
indexInSiblings: entry.indexInSiblings
|
|
2426
|
+
};
|
|
2427
|
+
return checkPrefix(newCursor, segments, segIdx - 1, segment.combinator, rootNode);
|
|
2302
2428
|
}
|
|
2303
2429
|
return false;
|
|
2304
2430
|
}
|
|
2305
|
-
for (let a2 = ancestors.length - 1; a2 >= 0; a2--) {
|
|
2306
|
-
const entry = ancestors[a2];
|
|
2431
|
+
for (let a2 = cursor.ancestors.length - 1; a2 >= 0; a2--) {
|
|
2432
|
+
const entry = cursor.ancestors[a2];
|
|
2307
2433
|
if (entry.kind !== ancestorKind) continue;
|
|
2308
2434
|
if (!matchesName(entry.name, segment)) continue;
|
|
2309
2435
|
if (segment.kind === "html" && !checkAttributes(entry.node, segment.attributes)) continue;
|
|
2310
2436
|
if (!checkDescendants(entry.node, segment.descendantChecks)) continue;
|
|
2311
|
-
if (
|
|
2312
|
-
|
|
2313
|
-
|
|
2437
|
+
if (!checkSelfNegations(entry.node, segment.selfNegations, rootNode)) continue;
|
|
2438
|
+
const newCursor = {
|
|
2439
|
+
ancestors: cursor.ancestors.slice(0, a2),
|
|
2440
|
+
siblings: entry.siblings,
|
|
2441
|
+
indexInSiblings: entry.indexInSiblings
|
|
2442
|
+
};
|
|
2443
|
+
if (checkPrefix(newCursor, segments, segIdx - 1, segment.combinator, rootNode)) return true;
|
|
2314
2444
|
}
|
|
2315
2445
|
return false;
|
|
2316
2446
|
}
|
|
2447
|
+
function isMatchableNode(node) {
|
|
2448
|
+
return HTML_ELEMENT_TYPES.has(node.type) || node.type === "mustache_section" || node.type === "mustache_inverted_section" || node.type === "mustache_interpolation" || node.type === "mustache_triple" || node.type === "mustache_comment" || node.type === "mustache_partial";
|
|
2449
|
+
}
|
|
2317
2450
|
function ancestorKindForSegment(segment) {
|
|
2318
2451
|
if (segment.rootOnly) return "root";
|
|
2319
2452
|
if (segment.kind === "html") return "html";
|
|
@@ -2347,12 +2480,13 @@ function getReportNode(node, rootNode) {
|
|
|
2347
2480
|
}
|
|
2348
2481
|
return node;
|
|
2349
2482
|
}
|
|
2350
|
-
function matchAlternative(rootNode, segments) {
|
|
2483
|
+
function matchAlternative(rootNode, segments, rootSiblings, rootIndexInSiblings) {
|
|
2351
2484
|
const results = [];
|
|
2352
2485
|
const lastSegment = segments[segments.length - 1];
|
|
2353
|
-
function walk(node, ancestors) {
|
|
2486
|
+
function walk(node, ancestors, siblings, indexInSiblings) {
|
|
2354
2487
|
if (nodeMatchesSegment(node, lastSegment, rootNode)) {
|
|
2355
|
-
|
|
2488
|
+
const cursor = { ancestors, siblings, indexInSiblings };
|
|
2489
|
+
if (segments.length === 1 || checkPrefix(cursor, segments, segments.length - 2, lastSegment.combinator, rootNode)) {
|
|
2356
2490
|
results.push(getReportNode(node, rootNode));
|
|
2357
2491
|
}
|
|
2358
2492
|
}
|
|
@@ -2361,19 +2495,28 @@ function matchAlternative(rootNode, segments) {
|
|
|
2361
2495
|
if (ancestorKind !== null) {
|
|
2362
2496
|
const name = ancestorKind === "html" ? getTagName(node)?.toLowerCase() : getSectionName(node)?.toLowerCase();
|
|
2363
2497
|
if (name) {
|
|
2364
|
-
newAncestors = [...ancestors, { kind: ancestorKind, name, node }];
|
|
2498
|
+
newAncestors = [...ancestors, { kind: ancestorKind, name, node, siblings, indexInSiblings }];
|
|
2365
2499
|
}
|
|
2366
2500
|
}
|
|
2367
|
-
for (
|
|
2501
|
+
for (let i2 = 0; i2 < node.children.length; i2++) {
|
|
2502
|
+
walk(node.children[i2], newAncestors, node.children, i2);
|
|
2503
|
+
}
|
|
2368
2504
|
}
|
|
2369
|
-
|
|
2505
|
+
const rootEntry = {
|
|
2506
|
+
kind: "root",
|
|
2507
|
+
name: "",
|
|
2508
|
+
node: rootNode,
|
|
2509
|
+
siblings: rootSiblings,
|
|
2510
|
+
indexInSiblings: rootIndexInSiblings
|
|
2511
|
+
};
|
|
2512
|
+
walk(rootNode, [rootEntry], rootSiblings, rootIndexInSiblings);
|
|
2370
2513
|
return results;
|
|
2371
2514
|
}
|
|
2372
|
-
function matchSelector(rootNode, selector) {
|
|
2515
|
+
function matchSelector(rootNode, selector, siblings = [], indexInSiblings = 0) {
|
|
2373
2516
|
const allResults = [];
|
|
2374
2517
|
const seen = /* @__PURE__ */ new Set();
|
|
2375
2518
|
for (const alt of selector) {
|
|
2376
|
-
for (const node of matchAlternative(rootNode, alt)) {
|
|
2519
|
+
for (const node of matchAlternative(rootNode, alt, siblings, indexInSiblings)) {
|
|
2377
2520
|
if (!seen.has(node)) {
|
|
2378
2521
|
seen.add(node);
|
|
2379
2522
|
allResults.push(node);
|
|
@@ -2535,6 +2678,23 @@ function collectErrors(tree, rules, customTagNames, customRules) {
|
|
|
2535
2678
|
);
|
|
2536
2679
|
}
|
|
2537
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
|
+
|
|
2538
2698
|
// src/core/diagnostic.ts
|
|
2539
2699
|
function toFix(r2) {
|
|
2540
2700
|
return { range: [r2.startIndex, r2.endIndex], newText: r2.newText };
|
|
@@ -2634,6 +2794,7 @@ var DEFAULT_EXCLUDE_SEGMENTS = ["/node_modules/", "/.git/"];
|
|
|
2634
2794
|
function resolveFiles(cliPatterns) {
|
|
2635
2795
|
const configPath = findConfigFile(process.cwd());
|
|
2636
2796
|
let config = null;
|
|
2797
|
+
const configDir = configPath ? import_node_path.default.dirname(configPath) : null;
|
|
2637
2798
|
if (configPath) {
|
|
2638
2799
|
try {
|
|
2639
2800
|
const text2 = import_node_fs.default.readFileSync(configPath, "utf-8");
|
|
@@ -2649,7 +2810,7 @@ function resolveFiles(cliPatterns) {
|
|
|
2649
2810
|
} else if (config?.include && config.include.length > 0) {
|
|
2650
2811
|
patterns = config.include;
|
|
2651
2812
|
} else {
|
|
2652
|
-
return { files: [], config };
|
|
2813
|
+
return { files: [], config, configDir };
|
|
2653
2814
|
}
|
|
2654
2815
|
let files = expandGlobs(patterns);
|
|
2655
2816
|
files = files.filter((f2) => !DEFAULT_EXCLUDE_SEGMENTS.some((seg) => f2.includes(seg)));
|
|
@@ -2667,7 +2828,7 @@ function resolveFiles(cliPatterns) {
|
|
|
2667
2828
|
}
|
|
2668
2829
|
files = files.filter((f2) => !excludeSet.has(f2));
|
|
2669
2830
|
}
|
|
2670
|
-
return { files, config };
|
|
2831
|
+
return { files, config, configDir };
|
|
2671
2832
|
}
|
|
2672
2833
|
function applyFixes(source, errors) {
|
|
2673
2834
|
const replacements = [];
|
|
@@ -2714,7 +2875,7 @@ async function run(args) {
|
|
|
2714
2875
|
}
|
|
2715
2876
|
const fixMode = args.includes("--fix");
|
|
2716
2877
|
const patterns = args.filter((a2) => a2 !== "--fix");
|
|
2717
|
-
const { files, config } = resolveFiles(patterns);
|
|
2878
|
+
const { files, config, configDir } = resolveFiles(patterns);
|
|
2718
2879
|
if (files.length === 0) {
|
|
2719
2880
|
if (patterns.length === 0 && (!config?.include || config.include.length === 0)) {
|
|
2720
2881
|
console.log(USAGE);
|
|
@@ -2736,12 +2897,15 @@ async function run(args) {
|
|
|
2736
2897
|
const rules = config?.rules;
|
|
2737
2898
|
const customTagNames = config?.customTags?.map((t2) => t2.name);
|
|
2738
2899
|
const customRules = config?.customRules;
|
|
2900
|
+
const ruleFilterBase = configDir ?? cwd;
|
|
2739
2901
|
for (const file of files) {
|
|
2740
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);
|
|
2741
2905
|
let source = import_node_fs.default.readFileSync(file, "utf-8");
|
|
2742
2906
|
if (fixMode) {
|
|
2743
2907
|
const tree2 = parseDocument(source);
|
|
2744
|
-
const errors2 = collectErrors2(tree2, displayPath, rules, customTagNames,
|
|
2908
|
+
const errors2 = collectErrors2(tree2, displayPath, rules, customTagNames, applicableCustomRules);
|
|
2745
2909
|
const fixed = applyFixes(source, errors2);
|
|
2746
2910
|
if (fixed !== source) {
|
|
2747
2911
|
import_node_fs.default.writeFileSync(file, fixed, "utf-8");
|
|
@@ -2749,7 +2913,7 @@ async function run(args) {
|
|
|
2749
2913
|
}
|
|
2750
2914
|
}
|
|
2751
2915
|
const tree = parseDocument(source);
|
|
2752
|
-
const errors = collectErrors2(tree, displayPath, rules, customTagNames,
|
|
2916
|
+
const errors = collectErrors2(tree, displayPath, rules, customTagNames, applicableCustomRules);
|
|
2753
2917
|
const fileErrors = errors.filter((e2) => e2.severity !== "warning");
|
|
2754
2918
|
const fileWarnings = errors.filter((e2) => e2.severity === "warning");
|
|
2755
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);
|