@fabriziosalmi/slopless 1.17.0 → 1.17.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/package.json CHANGED
@@ -1,11 +1,27 @@
1
1
  {
2
2
  "name": "@fabriziosalmi/slopless",
3
- "version": "1.17.0",
3
+ "version": "1.17.2",
4
4
  "description": "",
5
- "main": "dist/index.js",
5
+ "main": "dist/engine/api.js",
6
+ "types": "dist/engine/api.d.ts",
6
7
  "bin": {
7
8
  "slopless": "dist/index.js"
8
9
  },
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/engine/api.d.ts",
13
+ "default": "./dist/engine/api.js"
14
+ },
15
+ "./dist/engine/api": {
16
+ "types": "./dist/engine/api.d.ts",
17
+ "default": "./dist/engine/api.js"
18
+ },
19
+ "./dist/engine/api.js": {
20
+ "types": "./dist/engine/api.d.ts",
21
+ "default": "./dist/engine/api.js"
22
+ },
23
+ "./package.json": "./package.json"
24
+ },
9
25
  "files": [
10
26
  "dist",
11
27
  "rules"
@@ -20,8 +36,9 @@
20
36
  "docs:build": "npm run docs:gen && vitepress build docs",
21
37
  "docs:preview": "vitepress preview docs",
22
38
  "verify:bundle": "node scripts/verify-bundle.js",
39
+ "verify:rules": "node scripts/check-rule-performance.js",
23
40
  "verify:action": "bash scripts/run-action-locally.sh . 'src/**/*.ts'",
24
- "release:prep": "npm run docs:gen && npm run build && npm test && npm run verify:bundle",
41
+ "release:prep": "npm run docs:gen && npm run build && npm test && npm run verify:rules && npm run verify:bundle",
25
42
  "version": "node scripts/check-changelog.js && node scripts/sync-versions.js && npm run docs:gen && npm run build && git add dist docs README.md packages/*/package.json"
26
43
  },
27
44
  "keywords": [
@@ -6,7 +6,11 @@ tags:
6
6
  - accessibility
7
7
  - html
8
8
  match:
9
- regex: <div[^>]*\s(onclick|onClick)=(?!\{?\s*\(?\s*(?:e|ev|evt|event|_e)\s*\)?\s*=>\s*\{?\s*(?:e|ev|evt|event|_e)\.(?:stopPropagation|preventDefault)\(\)\s*\}?\s*\})
9
+ # `[^>]{0,2000}` rather than `[^>]*`: in markup with no closing `>` the
10
+ # unbounded form scanned to the end of the file from every `<div`, which is
11
+ # quadratic — 13ms at 15KB, 767ms at 120KB. An opening tag's attribute list is
12
+ # not 2,000 characters long, and exceeding it costs a missed finding.
13
+ regex: <div[^>]{0,2000}\s(onclick|onClick)=(?!\{?\s*\(?\s*(?:e|ev|evt|event|_e)\s*\)?\s*=>\s*\{?\s*(?:e|ev|evt|event|_e)\.(?:stopPropagation|preventDefault)\(\)\s*\}?\s*\})
10
14
  file_types:
11
15
  - html
12
16
  - js
@@ -8,7 +8,10 @@ tags:
8
8
  match:
9
9
  # `.only` is the dangerous one: the suite still passes, having run one test.
10
10
  # A skipped test is at least visibly skipped.
11
- regex: \b(?:describe|it|test|context|suite)\.only\s*\(|^\s*(?:fdescribe|fit)\s*\(
11
+ # `^[ \t]*`, not `^\s*`: with `m` every line start is a match position and
12
+ # `\s*` there swallows the newlines after it, which is quadratic on a file of
13
+ # blank lines. Indentation before `fit(` is horizontal whitespace.
14
+ regex: \b(?:describe|it|test|context|suite)\.only\s*\(|^[ \t]*(?:fdescribe|fit)\s*\(
12
15
  flags: m
13
16
  scan: code
14
17
  file_types:
@@ -8,8 +8,14 @@ match:
8
8
  # "Icon-only" is the whole claim: a button holding an icon *and* a visible
9
9
  # label already has an accessible name, and that is how most toolbars are
10
10
  # written. The svg has to be the only thing between the tags.
11
+ #
12
+ # The `{0,2000}?` bound is what stops the scan. Unbounded, an `<svg>` with no
13
+ # closing tag sent the search to the end of the file, once per candidate
14
+ # button: 15 seconds on 32,000 unclosed tags, and quadratic. Bounded, the same
15
+ # input is 179ms and linear. 2,000 characters is far more than a button icon's
16
+ # path data, and exceeding it costs a missed finding rather than a wrong one.
11
17
  regex: >-
12
- <button\b(?![^>]*(?:aria-label|aria-labelledby|title))[^>]*>\s*<svg\b[^>]*(?:/>|>(?:(?!</svg>)[\s\S])*?</svg>)\s*</button>
18
+ <button\b(?![^>]*(?:aria-label|aria-labelledby|title))[^>]*>\s*<svg\b[^>]*(?:/>|>(?:(?!</svg>)[\s\S]){0,2000}?</svg>)\s*</button>
13
19
  multiline: true
14
20
  file_types:
15
21
  - html
@@ -6,8 +6,13 @@ tags:
6
6
  - clean-code
7
7
  - style
8
8
  match:
9
+ # The gap between the two spellings is bounded at 400 characters. Unbounded,
10
+ # `.*?` rescanned the rest of the line from every candidate, which on one long
11
+ # line is quadratic: 128ms at 30KB, 413ms at 60KB. The CLI skips such files as
12
+ # generated, but `lintText` does not, so the editor and the MCP server see
13
+ # them. Two names on the same line are near each other or they are unrelated.
9
14
  regex: >-
10
- ((?<![.'\"])\b[a-z]+_[a-z]+\b(?!\s*[:?]).*?\b[a-z]+[A-Z][a-z]+\b)|(\b[a-z]+[A-Z][a-z]+\b.*?(?<![.'\"])\b[a-z]+_[a-z]+\b(?!\s*[:?]))
15
+ ((?<![.'\"])\b[a-z]+_[a-z]+\b(?!\s*[:?]).{0,400}?\b[a-z]+[A-Z][a-z]+\b)|(\b[a-z]+[A-Z][a-z]+\b.{0,400}?(?<![.'\"])\b[a-z]+_[a-z]+\b(?!\s*[:?]))
11
16
  file_types:
12
17
  - js
13
18
  - ts
@@ -5,8 +5,21 @@ category: clean-code
5
5
  tags:
6
6
  - clean-code
7
7
  match:
8
+ # `^[ \t]*` rather than `^\s*`: with the `m` flag every line start is a match
9
+ # position, and `\s*` there can swallow the newlines that follow it, so a file
10
+ # of blank lines gave the engine an ambiguous split at every one of them. That
11
+ # was quadratic — 28ms at 5,000 blank lines, 44s at 200,000 — and a file of
12
+ # blank lines is something anyone can put in a pull request. Indentation before
13
+ # `import` is horizontal whitespace, which is all this ever meant to allow.
14
+ #
15
+ # The gap between the two is bounded as well. Unbounded, every `import` with
16
+ # no `require` after it rescanned to the end of the file, which is quadratic
17
+ # on a file that is mostly imports: 13ms at 28KB, 848ms at 223KB. Bounded it is
18
+ # linear, 20ms and 146ms. 4,000 characters is roughly a hundred lines, which is
19
+ # further apart than the two spellings of a module system usually sit, and
20
+ # being further apart than that costs a missed warning rather than a wrong one.
8
21
  regex: >-
9
- ^\s*import\s[\s\S]*?^\s*(?:const|let|var)\s[^\n]*=\s*require\(|^\s*(?:const|let|var)\s[^\n]*=\s*require\([\s\S]*?^\s*import\s
22
+ ^[ \t]*import\s[\s\S]{0,4000}?^[ \t]*(?:const|let|var)\s[^\n]*=\s*require\(|^[ \t]*(?:const|let|var)\s[^\n]*=\s*require\([\s\S]{0,4000}?^[ \t]*import\s
10
23
  flags: m
11
24
  multiline: true
12
25
  file_types:
@@ -12,8 +12,31 @@ match:
12
12
  # Nothing starting with 255 is a host: 240.0.0.0/4 is reserved, so a dotted
13
13
  # quad there is a netmask or a broadcast address — `isInNet(host, "172.16.0.0",
14
14
  # "255.240.0.0")` names a mask, not a machine.
15
+ #
16
+ # The same reasoning covers the IANA special-purpose ranges that are not
17
+ # globally reachable (#43). They are what SSRF and egress blocklists list, so
18
+ # reporting them made a project's most defensive file its noisiest:
19
+ # 0.0.0.0/8 "this network", all of it, not only 0.0.0.0 (RFC 6890)
20
+ # 192.0.0.0/24 IETF protocol assignments (RFC 6890)
21
+ # 192.88.99.0/24 deprecated 6to4 relay anycast (RFC 7526)
22
+ # 100.64.0.0/10 shared address space, carrier-grade NAT (RFC 6598)
23
+ # 198.18.0.0/15 benchmarking (RFC 2544)
24
+ # The fire list keeps their neighbours, so the exclusion cannot widen quietly.
25
+ #
26
+ # RFC 1918 private ranges are deliberately still reported: 192.168.1.50 in
27
+ # code is exactly one environment. 169.254.0.0/16 is too, for the opposite
28
+ # reason from the above: a literal link-local address is almost always the
29
+ # cloud metadata service being called, not being blocked.
30
+ #
31
+ # Well-known public resolvers are not reported, by exact address and never by
32
+ # subnet: Google, Cloudflare (1.1.1.2 and 1.1.1.3 are its filtered variants),
33
+ # Quad9, OpenDNS and AdGuard. The message says an address pins code to one
34
+ # environment, and an anycast resolver reachable from everywhere does not —
35
+ # it is a documented choice that outlives the project, which is the call
36
+ # l0-git#29 made too. Decided in #43. The fire list keeps each one's
37
+ # neighbour, so a typo'd or unknown address beside them is still reported.
15
38
  regex: >-
16
- \b(?!127\.|0\.0\.0\.0\b|255\.|192\.0\.2\.|198\.51\.100\.|203\.0\.113\.)(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\b
39
+ \b(?!127\.|0\.|255\.|192\.0\.2\.|198\.51\.100\.|203\.0\.113\.|192\.0\.0\.|192\.88\.99\.|100\.(?:6[4-9]|[7-9][0-9]|1[01][0-9]|12[0-7])\.|198\.1[89]\.|(?:8\.8\.8\.8|8\.8\.4\.4|1\.1\.1\.1|1\.0\.0\.1|1\.1\.1\.2|1\.0\.0\.2|1\.1\.1\.3|1\.0\.0\.3|9\.9\.9\.9|149\.112\.112\.112|9\.9\.9\.10|149\.112\.112\.10|9\.9\.9\.11|149\.112\.112\.11|208\.67\.222\.222|208\.67\.220\.220|208\.67\.222\.220|208\.67\.220\.222|208\.67\.222\.123|208\.67\.220\.123|94\.140\.14\.14|94\.140\.15\.15|94\.140\.14\.15|94\.140\.15\.16|94\.140\.14\.140|94\.140\.14\.141)\b)(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\b
17
40
  scan: strings
18
41
  file_types:
19
42
  - js
@@ -52,7 +75,15 @@ message: >-
52
75
  tests:
53
76
  fire:
54
77
  - const host = '192.168.1.50';
55
- - const dns = "8.8.8.8";
78
+ # Beside a known resolver is not a known resolver.
79
+ - const dns = "8.8.8.9";
80
+ - const near = "1.1.1.4";
81
+ # One step outside each special-purpose range: still a host.
82
+ - const near = "192.0.1.1";
83
+ - const relay = "192.88.100.1";
84
+ - const below = "100.63.255.255";
85
+ - const above = "100.128.0.1";
86
+ - const bench = "198.20.0.1";
56
87
  quiet:
57
88
  - const host = process.env.DB_HOST;
58
89
  - const version = '1.2';
@@ -66,3 +97,15 @@ tests:
66
97
  # "255.240.0.0")` still reports — on the 172.16.0.0, which is an address.)
67
98
  - const netmask = "255.240.0.0";
68
99
  - const mask = '255.255.255.0';
100
+ # IANA special-purpose, not globally reachable (#43).
101
+ - '"192.0.0.0/24",'
102
+ - '"192.88.99.0/24",'
103
+ - const thisNet = "0.1.2.3";
104
+ - const cgnat = "100.64.0.1";
105
+ - const cgnatTop = "100.127.255.254";
106
+ - const benchmark = "198.18.0.1";
107
+ - const benchmarkTop = "198.19.255.1";
108
+ # Public anycast resolvers, a choice rather than an environment (#43).
109
+ - const dns = "8.8.8.8";
110
+ - const cloudflare = '1.1.1.3';
111
+ - 'upstream = ["9.9.9.9", "94.140.14.14"]'
@@ -2,11 +2,22 @@ id: VBC-920
2
2
  name: relative-link-format
3
3
  severity: warning
4
4
  category: docs
5
+ # Off unless the config names it (#43). In CommonMark [x](guide.md) and
6
+ # [x](./guide.md) resolve identically, and the message's "tool compatibility"
7
+ # names no tool, so this is a house style rather than a defect. Preferences
8
+ # nobody agreed to ship disabled; a project that wants the style can ask for it.
9
+ opt_in: true
5
10
  tags:
6
11
  - documentation
7
12
  - maintenance
8
13
  match:
9
- regex: \]\((?![a-z]+:|/|\.)
14
+ # `#` is excluded along with schemes, absolute paths and ./ ../: an in-page
15
+ # anchor points at a heading in this document, not at a relative file.
16
+ regex: \]\((?![a-z]+:|/|\.|#)
17
+ # Link syntax inside `code` or a fenced block is an example of a link, not a
18
+ # link, and CommonMark renders neither. The CHANGELOG line that describes how
19
+ # Jekyll writes links was the one reported (#43).
20
+ exclude_markdown_code: true
10
21
  file_types:
11
22
  - md
12
23
  message: >-
@@ -15,6 +26,22 @@ message: >-
15
26
  tests:
16
27
  fire:
17
28
  - See [the guide](guide.md).
29
+ # A real link after the code in the same line is still a real link.
30
+ - 'Run `make docs`, then read [the guide](guide.md).'
18
31
  quiet:
19
32
  - See [the guide](./guide.md).
20
33
  - See [the site](https://example.com).
34
+ # In-page anchor.
35
+ - See [the setup](#setup).
36
+ # Inside a code span.
37
+ - 'Inline code: `[CLI](cli-reference.html)` is how Jekyll links.'
38
+ - 'Double backticks: ``[a](b.md) with a ` inside`` still code.'
39
+ # Inside a fenced block, both fence characters.
40
+ - |
41
+ ```markdown
42
+ [guide](guide.md)
43
+ ```
44
+ - |
45
+ ~~~
46
+ [guide](guide.md)
47
+ ~~~
@@ -6,8 +6,11 @@ tags:
6
6
  - docs
7
7
  - maintenance
8
8
  match:
9
+ # `^[ \t]*`, not `^\s*`: with `m` every line start is a match position and
10
+ # `\s*` there swallows the newlines after it, which is quadratic on a file of
11
+ # blank lines. A blockquote marker is indented by horizontal whitespace.
9
12
  regex: >-
10
- \[\s*(?:EXPERIMENTAL|ALPHA|UNSTABLE|PREVIEW)\s*\]|(?:⚠️|:warning:)\s*\*{0,2}(?:experimental|alpha|unstable)\b|^\s*>\s*\*{0,2}(?:experimental|unstable)\b
13
+ \[\s*(?:EXPERIMENTAL|ALPHA|UNSTABLE|PREVIEW)\s*\]|(?:⚠️|:warning:)\s*\*{0,2}(?:experimental|alpha|unstable)\b|^[ \t]*>\s*\*{0,2}(?:experimental|unstable)\b
11
14
  flags: im
12
15
  file_types:
13
16
  - md