Skip to content

Commit 7ee134a

Browse files
Fix Windows tests for new postcss plugin (#14085)
This PR fixes the new `postcss-fix-relative-paths` plugin for Windows paths. The issue was that we mixed Windows-style path separators from the absolute file paths with the Posix-style separators from globs. This caused the `dirname` functions to resolve to the wrong files. To solve this, we now make the difference very clear by calling the content a `glob`. For globs, we always expect Posix-style path separators and for the case of making a glob absolute (by prefixing the directory), we now convert them into Posix-style explicitly. This PR also fixes an issue where negative rules (e.g. `!./**/*.ts`) were not properly rewritten. --------- Co-authored-by: Jordan Pittman <[email protected]>
1 parent b078327 commit 7ee134a

File tree

4 files changed

+71
-18
lines changed

4 files changed

+71
-18
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
@content "./**/*.ts";
2+
@content "!./**/*.ts";
23
@plugin "./plugin.js";
34
@plugin "./what\"s-this.js";

packages/internal-postcss-fix-relative-paths/src/index.test.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import fs from 'node:fs'
2+
import path from 'node:path'
23
import postcss from 'postcss'
34
import atImport from 'postcss-import'
45
import { describe, expect, test } from 'vitest'
56
import fixRelativePathsPlugin from '.'
67

78
describe('fixRelativePathsPlugin', () => {
89
test('rewrites @content and @plugin to be relative to the initial css file', async () => {
9-
let cssPath = `${__dirname}/fixtures/external-import/src/index.css`
10+
let cssPath = path.join(__dirname, 'fixtures', 'external-import', 'src', 'index.css')
1011
let css = fs.readFileSync(cssPath, 'utf-8')
1112

1213
let processor = postcss([atImport(), fixRelativePathsPlugin()])
@@ -15,13 +16,14 @@ describe('fixRelativePathsPlugin', () => {
1516

1617
expect(result.css.trim()).toMatchInlineSnapshot(`
1718
"@content "../../example-project/src/**/*.ts";
19+
@content "!../../example-project/src/**/*.ts";
1820
@plugin "../../example-project/src/plugin.js";
1921
@plugin "../../example-project/src/what\\"s-this.js";"
2022
`)
2123
})
2224

2325
test('should not rewrite non-relative paths', async () => {
24-
let cssPath = `${__dirname}/fixtures/external-import/src/invalid.css`
26+
let cssPath = path.join(__dirname, 'fixtures', 'external-import', 'src', 'invalid.css')
2527
let css = fs.readFileSync(cssPath, 'utf-8')
2628

2729
let processor = postcss([atImport(), fixRelativePathsPlugin()])
@@ -37,7 +39,7 @@ describe('fixRelativePathsPlugin', () => {
3739
})
3840

3941
test('should return relative paths even if the file is resolved in the same basedir as the root stylesheet', async () => {
40-
let cssPath = `${__dirname}/fixtures/external-import/src/plugins-in-root.css`
42+
let cssPath = path.join(__dirname, 'fixtures', 'external-import', 'src', 'plugins-in-root.css')
4143
let css = fs.readFileSync(cssPath, 'utf-8')
4244

4345
let processor = postcss([atImport(), fixRelativePathsPlugin()])

packages/internal-postcss-fix-relative-paths/src/index.ts

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import path from 'node:path'
2-
import type { AtRule, Container, Plugin } from 'postcss'
2+
import type { AtRule, Plugin } from 'postcss'
3+
import { normalizePath } from './normalize-path'
34

45
const SINGLE_QUOTE = "'"
56
const DOUBLE_QUOTE = '"'
@@ -9,12 +10,12 @@ export default function fixRelativePathsPlugin(): Plugin {
910
let touched: WeakSet<AtRule> = new WeakSet()
1011

1112
function fixRelativePath(atRule: AtRule) {
12-
let rootPath = getRoot(atRule)?.source?.input.file
13+
let rootPath = atRule.root().source?.input.file
1314
if (!rootPath) {
1415
return
1516
}
1617

17-
let inputFilePath = atRule?.source?.input.file
18+
let inputFilePath = atRule.source?.input.file
1819
if (!inputFilePath) {
1920
return
2021
}
@@ -34,15 +35,24 @@ export default function fixRelativePathsPlugin(): Plugin {
3435
if (!quote) {
3536
return
3637
}
37-
let content = atRule.params.slice(1, -1)
38+
let glob = atRule.params.slice(1, -1)
39+
40+
// Handle eventual negative rules. We only support one level of negation.
41+
let negativePrefix = ''
42+
if (glob.startsWith('!')) {
43+
glob = glob.slice(1)
44+
negativePrefix = '!'
45+
}
3846

3947
// We only want to rewrite relative paths.
40-
if (!content.startsWith('./') && !content.startsWith('../')) {
48+
if (!glob.startsWith('./') && !glob.startsWith('../')) {
4149
return
4250
}
4351

44-
let rulePath = path.posix.join(path.posix.dirname(inputFilePath), content)
45-
let relative = path.posix.relative(path.posix.dirname(rootPath), rulePath)
52+
let absoluteGlob = path.posix.join(normalizePath(path.dirname(inputFilePath)), glob)
53+
let absoluteRootPosixPath = path.posix.dirname(normalizePath(rootPath))
54+
55+
let relative = path.posix.relative(absoluteRootPosixPath, absoluteGlob)
4656

4757
// If the path points to a file in the same directory, `path.relative` will
4858
// remove the leading `./` and we need to add it back in order to still
@@ -51,17 +61,10 @@ export default function fixRelativePathsPlugin(): Plugin {
5161
relative = './' + relative
5262
}
5363

54-
atRule.params = quote + relative + quote
64+
atRule.params = quote + negativePrefix + relative + quote
5565
touched.add(atRule)
5666
}
5767

58-
function getRoot(node: AtRule | Container | undefined): Container | undefined {
59-
if (node?.parent) {
60-
return getRoot(node.parent as Container)
61-
}
62-
return node
63-
}
64-
6568
return {
6669
postcssPlugin: 'tailwindcss-postcss-fix-relative-paths',
6770
AtRule: {
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Inlined version of `normalize-path` <https://github.com/jonschlinkert/normalize-path>
2+
// Copyright (c) 2014-2018, Jon Schlinkert.
3+
// Released under the MIT License.
4+
function normalizePathBase(path: string, stripTrailing?: boolean) {
5+
if (typeof path !== 'string') {
6+
throw new TypeError('expected path to be a string')
7+
}
8+
9+
if (path === '\\' || path === '/') return '/'
10+
11+
var len = path.length
12+
if (len <= 1) return path
13+
14+
// ensure that win32 namespaces has two leading slashes, so that the path is
15+
// handled properly by the win32 version of path.parse() after being normalized
16+
// https://msdn.microsoft.com/library/windows/desktop/aa365247(v=vs.85).aspx#namespaces
17+
var prefix = ''
18+
if (len > 4 && path[3] === '\\') {
19+
var ch = path[2]
20+
if ((ch === '?' || ch === '.') && path.slice(0, 2) === '\\\\') {
21+
path = path.slice(2)
22+
prefix = '//'
23+
}
24+
}
25+
26+
var segs = path.split(/[/\\]+/)
27+
if (stripTrailing !== false && segs[segs.length - 1] === '') {
28+
segs.pop()
29+
}
30+
return prefix + segs.join('/')
31+
}
32+
33+
export function normalizePath(originalPath: string) {
34+
let normalized = normalizePathBase(originalPath)
35+
36+
// Make sure Windows network share paths are normalized properly
37+
// They have to begin with two slashes or they won't resolve correctly
38+
if (
39+
originalPath.startsWith('\\\\') &&
40+
normalized.startsWith('/') &&
41+
!normalized.startsWith('//')
42+
) {
43+
return `/${normalized}`
44+
}
45+
46+
return normalized
47+
}

0 commit comments

Comments
 (0)