@fabriziosalmi/slopless 1.16.0 → 1.17.1

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.16.0",
3
+ "version": "1.17.1",
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:
@@ -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