Skip to content

Commit 2fae007

Browse files
committed
style: add @e18e/eslint-plugin
1 parent 2280c38 commit 2fae007

13 files changed

Lines changed: 270 additions & 191 deletions

‎.changeset/free-shirts-notice.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
---
2+
---

‎.oxlintrc.json‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,27 @@
88
"env": {
99
"node": true
1010
},
11+
"jsPlugins": ["@e18e/eslint-plugin"],
1112
"plugins": ["import", "eslint", "node", "oxc", "vitest"],
1213
"rules": {
14+
"e18e/ban-dependencies": "error",
15+
"e18e/prefer-array-at": "error",
16+
"e18e/prefer-array-fill": "error",
17+
"e18e/prefer-array-from-map": "error",
18+
"e18e/prefer-array-some": "error",
19+
"e18e/prefer-array-to-reversed": "error",
20+
"e18e/prefer-array-to-sorted": "error",
21+
"e18e/prefer-array-to-spliced": "error",
22+
"e18e/prefer-date-now": "error",
23+
"e18e/prefer-includes": "error",
24+
"e18e/prefer-nullish-coalescing": "error",
25+
"e18e/prefer-object-has-own": "error",
26+
"e18e/prefer-regex-test": "error",
27+
"e18e/prefer-spread-syntax": "error",
28+
"e18e/prefer-static-regex": "error",
29+
"e18e/prefer-string-fromcharcode": "error",
30+
"e18e/prefer-timer-args": "error",
31+
"e18e/prefer-url-canparse": "error",
1332
"no-await-in-loop": "off"
1433
}
1534
}

‎lib/assertGitVersion.js‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,25 @@
11
/** @see {@link https://github.com/git/git/blob/master/Documentation/RelNotes/2.32.0.adoc} */
22
export const MIN_GIT_VERSION = '2.32.0'
33

4+
const GIT_VERSION_REGEXP = /git version\s+(\d+\.\d+\.\d+)/i
5+
46
/**
57
* @param {string} gitVersionOutput the Git version command output
68
* @returns {string} the Git version number in format `<major>.<minor>.<patch>`
79
*/
810
const extractGitVersionNumber = (gitVersionOutput) => {
9-
const match = gitVersionOutput.match(/git version\s+(\d+\.\d+\.\d+)/i)
11+
const match = gitVersionOutput.match(GIT_VERSION_REGEXP)
1012
return match?.[1]
1113
}
1214

15+
const SEMVER_REGEXP = /(\d+)\.(\d+)\.(\d+)/
16+
1317
/**
1418
* @param {string} version the version number in format `<major>.<minor>.<patch>`
1519
* @returns {[string, string, string]} the version number parsed as integers `[major, minor, patch]`
1620
*/
1721
const parseSemver = (version) => {
18-
const match = /(\d+)\.(\d+)\.(\d+)/.exec(version)
22+
const match = SEMVER_REGEXP.exec(version)
1923
return match?.slice(1, 4).map(Number)
2024
}
2125

‎lib/getSpawnedTask.js‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ export const createTaskError = (command, result, ctx, signal = 'FAILED') => {
5656
return new Error(`${red(command)} ${dim(`[${signal}]`)}`, { cause: result })
5757
}
5858

59+
const GIT_EXE_REGEXP = /^git(\.exe)?/i
60+
5961
/**
6062
* Returns the spawnable task.
6163
*
@@ -89,7 +91,7 @@ export const getSpawnedTask = ({
8991
nodeOptions: {
9092
// Only use topLevelDir as CWD if we are using the git binary
9193
// e.g `npm` should run tasks in the actual CWD
92-
cwd: /^git(\.exe)?/i.test(cmd) ? topLevelDir : cwd,
94+
cwd: GIT_EXE_REGEXP.test(cmd) ? topLevelDir : cwd,
9395
env: COLORS_ENABLED ? { FORCE_COLOR: 'true' } : { NO_COLOR: 'true' },
9496
},
9597
}
@@ -98,7 +100,7 @@ export const getSpawnedTask = ({
98100

99101
/** @param {ReturnType<typeof getInitialState>} ctx context */
100102
return async (ctx = getInitialState()) => {
101-
const result = exec(cmd, isFn ? args : args.concat(files), tinyExecOptions)
103+
const result = exec(cmd, isFn ? args : [...args, ...files], tinyExecOptions)
102104

103105
const taskFailed = () => result.exitCode > 0 || result.process?.signalCode
104106

‎lib/gitWorkflow.js‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,16 @@ const GIT_APPLY_ARGS = ['-v', '--whitespace=nowarn', '--recount', '--unidiff-zer
7070

7171
const calculateSha256 = (input) => crypto.createHash('sha256').update(input, 'utf-8').digest('hex')
7272

73+
const QUOTES_REGEXP = /^"(.*)"$/
74+
7375
/**
7476
* The lines are wrapped in double quotes
7577
* @returns {string[]}
7678
*/
77-
const cleanGitStashOutput = (lines) => lines.map((line) => line.replace(/^"(.*)"$/, '$1'))
79+
const cleanGitStashOutput = (lines) => lines.map((line) => line.replace(QUOTES_REGEXP, '$1'))
80+
81+
// oxlint-disable-next-line no-control-regex
82+
const GIT_STATUS_Z_REGEXP = /\x00(?=[ AMDRCU?!]{2} |$)/
7883

7984
export class GitWorkflow {
8085
/**
@@ -195,7 +200,7 @@ export class GitWorkflow {
195200
*/
196201
const unstagedFiles = status
197202
// oxlint-disable-next-line no-control-regex
198-
.split(/\x00(?=[ AMDRCU?!]{2} |$)/)
203+
.split(GIT_STATUS_Z_REGEXP)
199204
.filter((line) => {
200205
const [index, workingTree] = line
201206
const updatedInIndex = index !== ' ' && index !== '?'
@@ -373,7 +378,7 @@ export class GitWorkflow {
373378
: process.env.GIT_INDEX_FILE
374379

375380
const accessCheckedFiles = await Promise.allSettled(
376-
Array.from(this.matchedFiles).map(async (f) => {
381+
Array.from(this.matchedFiles, async (f) => {
377382
if (f.status === 'D') {
378383
await fs.access(f.filepath)
379384
return f.filepath // File is no longer deleted and can be added

‎lib/groupFilesByConfig.js‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ export const groupFilesByConfig = async ({ configs, files, singleConfigMode }) =
5555
filesSet.delete(file)
5656
})
5757

58-
filesByConfig[filepath] = { config, files: Array.from(scopedFiles) }
58+
filesByConfig[filepath] = { config, files: [...scopedFiles] }
5959
}
6060

6161
return filesByConfig

‎lib/normalizePath.js‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ const WIN32_FILE_NS = '\\\\?\\'
2424
*/
2525
const WIN32_DEVICE_NS = '\\\\.\\'
2626

27+
const PATH_SEP_REGEXP = /[/\\]+/
28+
29+
const WIN32_NS_REGEXP = /^\/(\.|\?)/
30+
2731
/**
2832
* Normalize input file path to use POSIX separators
2933
* @param {String} input
@@ -34,11 +38,11 @@ export const normalizePath = (input) => {
3438
return path.posix.sep
3539
}
3640

37-
let normalized = input.split(/[/\\]+/).join(path.posix.sep)
41+
let normalized = input.split(PATH_SEP_REGEXP).join(path.posix.sep)
3842

3943
/** Handle win32 Namespaced paths by changing e.g. \\.\ to //./ */
4044
if (input.startsWith(WIN32_FILE_NS) || input.startsWith(WIN32_DEVICE_NS)) {
41-
normalized = normalized.replace(/^\/(\.|\?)/, '//$1')
45+
normalized = normalized.replace(WIN32_NS_REGEXP, '//$1')
4246
}
4347

4448
/** Remove trailing slash */

‎lib/parseGitZOutput.js‎

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
1+
const GIT_Z_REGEXP = /\u0000$/ // oxlint-disable-line no-control-regex
2+
13
/**
24
* Return array of strings split from the output of `git <something> -z`.
35
* With `-z`, git prints `fileA\u0000fileB\u0000fileC\u0000` so we need to
46
* remove the last occurrence of `\u0000` before splitting
57
*/
68
export const parseGitZOutput = (input) =>
7-
input
8-
? input
9-
.replace(/\u0000$/, '') // oxlint-disable-line no-control-regex
10-
.split('\u0000')
11-
: []
9+
input ? input.replace(GIT_Z_REGEXP, '').split('\u0000') : []

‎lib/searchConfigs.js‎

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
/** @typedef {import('./index').Logger} Logger */
2-
31
import fs, { constants } from 'node:fs/promises'
42
import path from 'node:path'
53

@@ -13,15 +11,14 @@ import { validateConfig } from './validateConfig.js'
1311

1412
const debugLog = createDebug('lint-staged:searchConfigs')
1513

16-
const EXEC_GIT = ['ls-files', '-z', '--full-name', '-t']
17-
1814
const CONFIG_PATHSPEC = CONFIG_FILE_NAMES.map((f) => `:(glob)**/${f}`)
1915

2016
const numberOfLevels = (file) => file.split('/').length
2117

22-
const sortAlphabetically = (a, b) => a.localeCompare(b)
18+
// Sort deepest first, then alphabetically
19+
const compareConfigPaths = (a, b) => numberOfLevels(b) - numberOfLevels(a) || a.localeCompare(b)
2320

24-
const sortDeepestParth = (a, b) => (numberOfLevels(a) > numberOfLevels(b) ? -1 : 1)
21+
const GIT_STATUS_REGEXP = /^[HSMRCK?U] /
2522

2623
/**
2724
* Get all possible config files from git
@@ -34,7 +31,10 @@ const sortDeepestParth = (a, b) => (numberOfLevels(a) > numberOfLevels(b) ? -1 :
3431
const listConfigFilesFromGit = async ({ cwd, topLevelDir }) =>
3532
execGit(
3633
[
37-
...EXEC_GIT,
34+
'ls-files',
35+
'-z',
36+
'--full-name',
37+
'-t',
3838
'--cached', // show all tracked files
3939
'--others', // show untracked files
4040
'--exclude-standard', // apply standard git exclusions (.gitignore, etc.)
@@ -55,7 +55,7 @@ const listConfigFilesFromGit = async ({ cwd, topLevelDir }) =>
5555
return []
5656
}
5757

58-
const relativePath = line.replace(/^[HSMRCK?U] /, '')
58+
const relativePath = line.replace(GIT_STATUS_REGEXP, '')
5959
const absolutePath = normalizePath(path.join(topLevelDir, relativePath))
6060
return [absolutePath]
6161
})
@@ -110,7 +110,7 @@ export const listConfigFilesFromFs = async ({ cwd }) => {
110110
* @param {string} [options.configPath] - Explicit path to a config file
111111
* @param {string} [options.cwd] - Current working directory
112112
* @param {string} [options.topLevelDir] - Top-level directory of the git repo
113-
* @param {Logger} logger
113+
* @param {import('./index').Logger} logger
114114
*
115115
* @returns {Promise<{ [key: string]: { config: *, files: string[] } }>} found configs with filepath as key, and config as value
116116
*/
@@ -151,9 +151,8 @@ export const searchConfigs = async (
151151
])
152152

153153
/** Create object with key as config file, and value as null */
154-
const configs = Array.from(possibleConfigFiles)
155-
.sort(sortAlphabetically)
156-
.sort(sortDeepestParth)
154+
const configs = [...possibleConfigFiles]
155+
.toSorted(compareConfigPaths)
157156
.reduce((acc, configPath) => Object.assign(acc, { [configPath]: null }), {})
158157

159158
/** Load and validate all configs to the above object */

‎lib/validateBraces.js‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ import { incorrectBraces } from './messages.js'
3131
export const getIncorrectBracesRegexp = () =>
3232
/(?<![\\$])({)(?:(?!(?<!\\),|\.\.|\{|\}).)*?(?<!\\)(})/g
3333

34+
const BRACE_OPEN_REGEXP = /{/
35+
const BRACE_CLOSE_REGEXP = /}/
36+
3437
/**
3538
* @param {string} pattern
3639
* @returns {string}
@@ -42,7 +45,7 @@ const stripIncorrectBraces = (pattern) => {
4245

4346
while (match) {
4447
const fullMatch = match[0]
45-
const withoutBraces = fullMatch.replace(/{/, '').replace(/}/, '')
48+
const withoutBraces = fullMatch.replace(BRACE_OPEN_REGEXP, '').replace(BRACE_CLOSE_REGEXP, '')
4649
output = output.replace(fullMatch, withoutBraces)
4750
regexp.lastIndex = 0 // restart iteration
4851
match = regexp.exec(output)

0 commit comments

Comments
 (0)