Skip to content

Commit

Permalink
Fix Windows tests for new postcss plugin (#14085)
Browse files Browse the repository at this point in the history
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 <jordan@cryptica.me>
  • Loading branch information
philipp-spiess and thecrypticace authored Jul 30, 2024
1 parent b078327 commit 7ee134a
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 18 deletions.
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
@content "./**/*.ts";
@content "!./**/*.ts";
@plugin "./plugin.js";
@plugin "./what\"s-this.js";
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import fs from 'node:fs'
import path from 'node:path'
import postcss from 'postcss'
import atImport from 'postcss-import'
import { describe, expect, test } from 'vitest'
import fixRelativePathsPlugin from '.'

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

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

expect(result.css.trim()).toMatchInlineSnapshot(`
"@content "../../example-project/src/**/*.ts";
@content "!../../example-project/src/**/*.ts";
@plugin "../../example-project/src/plugin.js";
@plugin "../../example-project/src/what\\"s-this.js";"
`)
})

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

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

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

let processor = postcss([atImport(), fixRelativePathsPlugin()])
Expand Down
33 changes: 18 additions & 15 deletions packages/internal-postcss-fix-relative-paths/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import path from 'node:path'
import type { AtRule, Container, Plugin } from 'postcss'
import type { AtRule, Plugin } from 'postcss'
import { normalizePath } from './normalize-path'

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

function fixRelativePath(atRule: AtRule) {
let rootPath = getRoot(atRule)?.source?.input.file
let rootPath = atRule.root().source?.input.file
if (!rootPath) {
return
}

let inputFilePath = atRule?.source?.input.file
let inputFilePath = atRule.source?.input.file
if (!inputFilePath) {
return
}
Expand All @@ -34,15 +35,24 @@ export default function fixRelativePathsPlugin(): Plugin {
if (!quote) {
return
}
let content = atRule.params.slice(1, -1)
let glob = atRule.params.slice(1, -1)

// Handle eventual negative rules. We only support one level of negation.
let negativePrefix = ''
if (glob.startsWith('!')) {
glob = glob.slice(1)
negativePrefix = '!'
}

// We only want to rewrite relative paths.
if (!content.startsWith('./') && !content.startsWith('../')) {
if (!glob.startsWith('./') && !glob.startsWith('../')) {
return
}

let rulePath = path.posix.join(path.posix.dirname(inputFilePath), content)
let relative = path.posix.relative(path.posix.dirname(rootPath), rulePath)
let absoluteGlob = path.posix.join(normalizePath(path.dirname(inputFilePath)), glob)
let absoluteRootPosixPath = path.posix.dirname(normalizePath(rootPath))

let relative = path.posix.relative(absoluteRootPosixPath, absoluteGlob)

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

atRule.params = quote + relative + quote
atRule.params = quote + negativePrefix + relative + quote
touched.add(atRule)
}

function getRoot(node: AtRule | Container | undefined): Container | undefined {
if (node?.parent) {
return getRoot(node.parent as Container)
}
return node
}

return {
postcssPlugin: 'tailwindcss-postcss-fix-relative-paths',
AtRule: {
Expand Down
47 changes: 47 additions & 0 deletions packages/internal-postcss-fix-relative-paths/src/normalize-path.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Inlined version of `normalize-path` <https://github.com/jonschlinkert/normalize-path>
// Copyright (c) 2014-2018, Jon Schlinkert.
// Released under the MIT License.
function normalizePathBase(path: string, stripTrailing?: boolean) {
if (typeof path !== 'string') {
throw new TypeError('expected path to be a string')
}

if (path === '\\' || path === '/') return '/'

var len = path.length
if (len <= 1) return path

// ensure that win32 namespaces has two leading slashes, so that the path is
// handled properly by the win32 version of path.parse() after being normalized
// https://msdn.microsoft.com/library/windows/desktop/aa365247(v=vs.85).aspx#namespaces
var prefix = ''
if (len > 4 && path[3] === '\\') {
var ch = path[2]
if ((ch === '?' || ch === '.') && path.slice(0, 2) === '\\\\') {
path = path.slice(2)
prefix = '//'
}
}

var segs = path.split(/[/\\]+/)
if (stripTrailing !== false && segs[segs.length - 1] === '') {
segs.pop()
}
return prefix + segs.join('/')
}

export function normalizePath(originalPath: string) {
let normalized = normalizePathBase(originalPath)

// Make sure Windows network share paths are normalized properly
// They have to begin with two slashes or they won't resolve correctly
if (
originalPath.startsWith('\\\\') &&
normalized.startsWith('/') &&
!normalized.startsWith('//')
) {
return `/${normalized}`
}

return normalized
}

0 comments on commit 7ee134a

Please sign in to comment.