Skip to content

Commit

Permalink
refactor: reimplement "normalize-path"
Browse files Browse the repository at this point in the history
  • Loading branch information
iiroj committed Aug 11, 2023
1 parent bc2d267 commit 44a4f6c
Show file tree
Hide file tree
Showing 18 changed files with 180 additions and 73 deletions.
5 changes: 3 additions & 2 deletions lib/chunkFiles.js
@@ -1,7 +1,8 @@
import path from 'node:path'

import debug from 'debug'
import normalize from 'normalize-path'

import { normalizePath } from './normalizePath.js'

const debugLog = debug('lint-staged:chunkFiles')

Expand Down Expand Up @@ -35,7 +36,7 @@ const chunkArray = (arr, chunkCount) => {
*/
export const chunkFiles = ({ files, baseDir, maxArgLength = null, relative = false }) => {
const normalizedFiles = files.map((file) =>
normalize(relative || !baseDir ? file : path.resolve(baseDir, file))
normalizePath(relative || !baseDir ? file : path.resolve(baseDir, file))
)

if (!maxArgLength) {
Expand Down
7 changes: 4 additions & 3 deletions lib/generateTasks.js
Expand Up @@ -2,7 +2,8 @@ import path from 'node:path'

import debug from 'debug'
import micromatch from 'micromatch'
import normalize from 'normalize-path'

import { normalizePath } from './normalizePath.js'

const debugLog = debug('lint-staged:generateTasks')

Expand All @@ -19,7 +20,7 @@ const debugLog = debug('lint-staged:generateTasks')
export const generateTasks = ({ config, cwd = process.cwd(), files, relative = false }) => {
debugLog('Generating linter tasks')

const relativeFiles = files.map((file) => normalize(path.relative(cwd, file)))
const relativeFiles = files.map((file) => normalizePath(path.relative(cwd, file)))

return Object.entries(config).map(([pattern, commands]) => {
const isParentDirPattern = pattern.startsWith('../')
Expand All @@ -42,7 +43,7 @@ export const generateTasks = ({ config, cwd = process.cwd(), files, relative = f
strictBrackets: true,
})

const fileList = matches.map((file) => normalize(relative ? file : path.resolve(cwd, file)))
const fileList = matches.map((file) => normalizePath(relative ? file : path.resolve(cwd, file)))

const task = { pattern, commands, fileList }
debugLog('Generated task: \n%O', task)
Expand Down
5 changes: 2 additions & 3 deletions lib/getStagedFiles.js
@@ -1,17 +1,16 @@
import path from 'node:path'

import normalize from 'normalize-path'

import { execGit } from './execGit.js'
import { getDiffCommand } from './getDiffCommand.js'
import { normalizePath } from './normalizePath.js'
import { parseGitZOutput } from './parseGitZOutput.js'

export const getStagedFiles = async ({ cwd = process.cwd(), diff, diffFilter } = {}) => {
try {
const lines = await execGit(getDiffCommand(diff, diffFilter), { cwd })
if (!lines) return []

return parseGitZOutput(lines).map((file) => normalize(path.resolve(cwd, file)))
return parseGitZOutput(lines).map((file) => normalizePath(path.resolve(cwd, file)))
} catch {
return null
}
Expand Down
50 changes: 50 additions & 0 deletions lib/normalizePath.js
@@ -0,0 +1,50 @@
/**
* Reimplementation of "normalize-path"
* @see https://github.com/jonschlinkert/normalize-path/blob/52c3a95ebebc2d98c1ad7606cbafa7e658656899/index.js
*/

/*!
* normalize-path <https://github.com/jonschlinkert/normalize-path>
*
* Copyright (c) 2014-2018, Jon Schlinkert.
* Released under the MIT License.
*/

import path from 'node:path'

/**
* A file starting with \\?\
* @see https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file#win32-file-namespaces
*/
const WIN32_FILE_NS = '\\\\?\\'

/**
* A file starting with \\.\
* @see https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file#win32-file-namespaces
*/
const WIN32_DEVICE_NS = '\\\\.\\'

/**
* Normalize input file path to use POSIX separators
* @param {String} input
* @returns String
*/
export const normalizePath = (input) => {
if (input === path.posix.sep || input === path.win32.sep) {
return path.posix.sep
}

let normalized = input.split(/[/\\]+/).join(path.posix.sep)

/** Handle win32 Namespaced paths by changing e.g. \\.\ to //./ */
if (input.startsWith(WIN32_FILE_NS) || input.startsWith(WIN32_DEVICE_NS)) {
normalized = normalized.replace(/^\/(\.|\?)/, '//$1')
}

/** Remove trailing slash */
if (normalized.endsWith(path.posix.sep)) {
normalized = normalized.slice(0, -1)
}

return normalized
}
14 changes: 7 additions & 7 deletions lib/resolveGitRepo.js
Expand Up @@ -2,10 +2,10 @@ import fs from 'node:fs/promises'
import path from 'node:path'

import debug from 'debug'
import normalize from 'normalize-path'

import { execGit } from './execGit.js'
import { readFile } from './file.js'
import { normalizePath } from './normalizePath.js'

const debugLog = debug('lint-staged:resolveGitRepo')

Expand All @@ -15,7 +15,7 @@ const debugLog = debug('lint-staged:resolveGitRepo')
*/
const resolveGitConfigDir = async (gitDir) => {
// Get the real path in case it's a symlink
const defaultDir = normalize(await fs.realpath(path.join(gitDir, '.git')))
const defaultDir = normalizePath(await fs.realpath(path.join(gitDir, '.git')))
const stats = await fs.lstat(defaultDir)
// If .git is a directory, use it
if (stats.isDirectory()) return defaultDir
Expand All @@ -33,10 +33,10 @@ export const determineGitDir = (cwd, relativeDir) => {
}
if (relativeDir) {
// the current working dir is inside the git top-level directory
return normalize(cwd.substring(0, cwd.lastIndexOf(relativeDir)))
return normalizePath(cwd.substring(0, cwd.lastIndexOf(relativeDir)))
} else {
// the current working dir is the top-level git directory
return normalize(cwd)
return normalizePath(cwd)
}
}

Expand All @@ -55,9 +55,9 @@ export const resolveGitRepo = async (cwd = process.cwd()) => {

// read the path of the current directory relative to the top-level directory
// don't read the toplevel directly, it will lead to an posix conform path on non posix systems (cygwin)
const gitRel = normalize(await execGit(['rev-parse', '--show-prefix'], { cwd }))
const gitDir = determineGitDir(normalize(cwd), gitRel)
const gitConfigDir = normalize(await resolveGitConfigDir(gitDir))
const gitRel = normalizePath(await execGit(['rev-parse', '--show-prefix'], { cwd }))
const gitDir = determineGitDir(normalizePath(cwd), gitRel)
const gitConfigDir = normalizePath(await resolveGitConfigDir(gitDir))

debugLog('Resolved git directory to be `%s`', gitDir)
debugLog('Resolved git config directory to be `%s`', gitConfigDir)
Expand Down
6 changes: 3 additions & 3 deletions lib/runAll.js
Expand Up @@ -5,7 +5,6 @@ import path from 'node:path'
import chalk from 'chalk'
import debug from 'debug'
import { Listr } from 'listr2'
import normalize from 'normalize-path'

import { chunkFiles } from './chunkFiles.js'
import { execGit } from './execGit.js'
Expand All @@ -24,6 +23,7 @@ import {
SKIPPED_GIT_ERROR,
skippingBackup,
} from './messages.js'
import { normalizePath } from './normalizePath.js'
import { resolveGitRepo } from './resolveGitRepo.js'
import {
applyModificationsSkipped,
Expand Down Expand Up @@ -160,7 +160,7 @@ export const runAll = async (
const matchedFiles = new Set()

for (const [configPath, { config, files }] of Object.entries(filesByConfig)) {
const configName = configPath ? normalize(path.relative(cwd, configPath)) : 'Config object'
const configName = configPath ? normalizePath(path.relative(cwd, configPath)) : 'Config object'

const stagedFileChunks = chunkFiles({ baseDir: gitDir, files, maxArgLength, relative })

Expand Down Expand Up @@ -193,7 +193,7 @@ export const runAll = async (
// relative filenames in the entire set.
const normalizedFile = path.isAbsolute(file)
? file
: normalize(path.join(groupCwd, file))
: normalizePath(path.join(groupCwd, file))

matchedFiles.add(normalizedFile)
})
Expand Down
6 changes: 3 additions & 3 deletions lib/searchConfigs.js
Expand Up @@ -3,10 +3,10 @@
import path from 'node:path'

import debug from 'debug'
import normalize from 'normalize-path'

import { execGit } from './execGit.js'
import { loadConfig, searchPlaces } from './loadConfig.js'
import { normalizePath } from './normalizePath.js'
import { parseGitZOutput } from './parseGitZOutput.js'
import { validateConfig } from './validateConfig.js'

Expand All @@ -21,7 +21,7 @@ const numberOfLevels = (file) => file.split('/').length

const sortDeepestParth = (a, b) => (numberOfLevels(a) > numberOfLevels(b) ? -1 : 1)

const isInsideDirectory = (dir) => (file) => file.startsWith(normalize(dir))
const isInsideDirectory = (dir) => (file) => file.startsWith(normalizePath(dir))

/**
* Search all config files from the git repository, preferring those inside `cwd`.
Expand Down Expand Up @@ -68,7 +68,7 @@ export const searchConfigs = async (

/** Sort possible config files so that deepest is first */
const possibleConfigFiles = [...cachedFiles, ...otherFiles]
.map((file) => normalize(path.join(gitDir, file)))
.map((file) => normalizePath(path.join(gitDir, file)))
.filter(isInsideDirectory(cwd))
.sort(sortDeepestParth)

Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion package.json
Expand Up @@ -41,7 +41,6 @@
"lilconfig": "2.1.0",
"listr2": "6.6.1",
"micromatch": "4.0.5",
"normalize-path": "3.0.0",
"object-inspect": "1.12.3",
"pidtree": "0.6.0",
"string-argv": "0.3.2",
Expand Down
4 changes: 2 additions & 2 deletions test/integration/__utils__/createTempDir.js
Expand Up @@ -3,7 +3,7 @@ import fs from 'node:fs/promises'
import os from 'node:os'
import path from 'node:path'

import normalize from 'normalize-path'
import { normalizePath } from '../../../lib/normalizePath.js'

/**
* Create temporary random directory and return its path
Expand All @@ -17,5 +17,5 @@ export const createTempDir = async () => {
const tempDir = path.join(baseDir, 'lint-staged', crypto.randomUUID())
await fs.mkdir(tempDir, { recursive: true })

return normalize(tempDir)
return normalizePath(tempDir)
}
7 changes: 4 additions & 3 deletions test/integration/multiple-config-files.test.js
Expand Up @@ -4,7 +4,8 @@ import './__mocks__/dynamicImport.js'
import path from 'node:path'

import { jest as jestGlobals } from '@jest/globals'
import normalize from 'normalize-path'

import { normalizePath } from '../../lib/normalizePath.js'

import { withGitIntegration } from './__utils__/withGitIntegration.js'

Expand Down Expand Up @@ -84,11 +85,11 @@ describe('lint-staged', () => {
expect(await readFile('deeper/even/file.js')).toMatch('file.js')

// 'deeper/even/deeper/file.js' is relative to parent 'deeper/even/'
expect(await readFile('deeper/even/deeper/file.js')).toMatch(normalize('deeper/file.js'))
expect(await readFile('deeper/even/deeper/file.js')).toMatch(normalizePath('deeper/file.js'))

// 'a/very/deep/file/path/file.js' is relative to root '.'
expect(await readFile('a/very/deep/file/path/file.js')).toMatch(
normalize('a/very/deep/file/path/file.js')
normalizePath('a/very/deep/file/path/file.js')
)
})
)
Expand Down
11 changes: 5 additions & 6 deletions test/unit/chunkFiles.spec.js
@@ -1,12 +1,11 @@
import path from 'node:path'

import normalize from 'normalize-path'

import { chunkFiles } from '../../lib/chunkFiles.js'
import { normalizePath } from '../../lib/normalizePath.js'

describe('chunkFiles', () => {
const files = ['example.js', 'foo.js', 'bar.js', 'foo/bar.js']
const baseDir = normalize('/opt/git/example.git')
const baseDir = normalizePath('/opt/git/example.git')

it('should default to sane value', () => {
const chunkedFiles = chunkFiles({ baseDir, files: ['foo.js'], relative: true })
Expand All @@ -20,7 +19,7 @@ describe('chunkFiles', () => {

it('should chunk too long argument string', () => {
const chunkedFiles = chunkFiles({ baseDir, files, maxArgLength: 20, relative: false })
expect(chunkedFiles).toEqual(files.map((file) => [normalize(path.resolve(baseDir, file))]))
expect(chunkedFiles).toEqual(files.map((file) => [normalizePath(path.resolve(baseDir, file))]))
})

it('should take into account relative setting', () => {
Expand All @@ -33,11 +32,11 @@ describe('chunkFiles', () => {

it('should resolve absolute paths by default', () => {
const chunkedFiles = chunkFiles({ baseDir, files })
expect(chunkedFiles).toEqual([files.map((file) => normalize(path.resolve(baseDir, file)))])
expect(chunkedFiles).toEqual([files.map((file) => normalizePath(path.resolve(baseDir, file)))])
})

it('should resolve absolute paths by default even when maxArgLength is set', () => {
const chunkedFiles = chunkFiles({ baseDir, files, maxArgLength: 262144 })
expect(chunkedFiles).toEqual([files.map((file) => normalize(path.resolve(baseDir, file)))])
expect(chunkedFiles).toEqual([files.map((file) => normalizePath(path.resolve(baseDir, file)))])
})
})

0 comments on commit 44a4f6c

Please sign in to comment.