Skip to content

Commit abc7548

Browse files
authored
fix: stdin urls on the command line (#6345)
1 parent c52ca70 commit abc7548

File tree

6 files changed

+86
-24
lines changed

6 files changed

+86
-24
lines changed

packages/cspell/package.json

+2-3
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@
4343
"build": "tsc -p . && pnpm run build:api",
4444
"build:api": "rollup -c rollup.config.mjs",
4545
"build:esm": "tsc -p .",
46-
"build:lib": "tsc -b src/lib/tsconfig.json -f",
4746
"build:readme": "pnpm build:readme:help",
4847
"build:readme:help": "pnpm build:readme:help:lint && pnpm build:readme:help:trace && inject-markdown README.md && prettier -w README.md",
4948
"build:readme:help:lint": "./bin.mjs lint --help > static/help-lint.txt",
@@ -52,8 +51,8 @@
5251
"coverage": "vitest run --coverage",
5352
"test:watch": "vitest",
5453
"test": "vitest run",
55-
"watch": "tsc -b . -w -f",
56-
"compile": "tsc -b . -f",
54+
"watch": "tsc -p . -w",
55+
"compile": "tsc -p .",
5756
"test-watch": "vitest",
5857
"version": "node ./tools/patch-version.mjs && git add .",
5958
"prepublishOnly": "pnpm run clean-build",
+3-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export const UTF8 = 'utf8' as const;
22
export const STDIN = 'stdin' as const;
3-
export const STDINProtocol = 'stdin://' as const;
4-
export const FileProtocol = 'file://' as const;
3+
export const STDINProtocol = 'stdin:' as const;
4+
export const STDINUrlPrefix = 'stdin://' as const;
5+
export const FileUrlPrefix = 'file://' as const;

packages/cspell/src/app/util/fileHelper.test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as path from 'node:path';
2-
import { fileURLToPath } from 'node:url';
2+
import { fileURLToPath, pathToFileURL } from 'node:url';
33

44
import getStdin from 'get-stdin';
55
import { afterEach, describe, expect, test, vi } from 'vitest';
@@ -119,7 +119,7 @@ describe('fileHelper', () => {
119119
${'not_found'} | ${__dirname} | ${path.join(__dirname, 'not_found')}
120120
${'not_found'} | ${undefined} | ${path.resolve('not_found')}
121121
${'stdin'} | ${undefined} | ${'stdin://'}
122-
${'stdin://source.ts'} | ${undefined} | ${'stdin://' + path.resolve('source.ts')}
122+
${'stdin://source.ts'} | ${undefined} | ${pathToFileURL('source.ts').href.replace(/^file:/, 'stdin:')}
123123
`('resolveFilename $filename $cwd', async ({ filename, cwd, expected }) => {
124124
expect(resolveFilename(filename, cwd)).toBe(expected);
125125
});

packages/cspell/src/app/util/fileHelper.ts

+20-17
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,12 @@ import { fileToDocument, isBinaryFile as isUriBinaryFile } from 'cspell-lib';
1111
import getStdin from 'get-stdin';
1212

1313
import { asyncAwait, asyncFlatten, asyncMap, asyncPipe, mergeAsyncIterables } from './async.js';
14-
import { FileProtocol, STDIN, STDINProtocol, UTF8 } from './constants.js';
14+
import { FileUrlPrefix, STDIN, STDINProtocol, STDINUrlPrefix, UTF8 } from './constants.js';
1515
import { IOError, toApplicationError, toError } from './errors.js';
1616
import type { GlobOptions } from './glob.js';
1717
import { globP } from './glob.js';
1818
import { readStdin } from './stdin.js';
19+
import { isStdinUrl, resolveStdinUrl } from './stdinUrl.js';
1920
import { clean } from './util.js';
2021

2122
export interface ConfigInfo {
@@ -67,7 +68,7 @@ export function fileInfoToDocument(
6768
languageId = languageId || undefined;
6869
locale = locale || undefined;
6970

70-
const uri = filenameToUrlString(filename);
71+
const uri = filenameToUrl(filename);
7172

7273
if (uri.href.startsWith(STDINProtocol)) {
7374
return clean({
@@ -81,18 +82,17 @@ export function fileInfoToDocument(
8182
return fileToDocument(uri.href, text, languageId, locale);
8283
}
8384

84-
export function filenameToUrlString(filename: string, cwd = '.'): URL {
85+
export function filenameToUrl(filename: string, cwd = '.'): URL {
8586
const cwdURL = toFileDirURL(cwd);
8687
if (filename === STDIN) return new URL('stdin:///');
87-
if (filename.startsWith(STDINProtocol)) {
88-
const filePath = filename.slice(STDINProtocol.length);
89-
return toFileURL(filePath, cwdURL);
88+
if (isStdinUrl(filename)) {
89+
return new URL(resolveStdinUrl(filename, cwd));
9090
}
9191
return toFileURL(filename, cwdURL);
9292
}
9393

9494
export function filenameToUri(filename: string, cwd?: string): URL {
95-
return toURL(filenameToUrlString(filename, cwd));
95+
return toURL(filenameToUrl(filename, cwd));
9696
}
9797

9898
export function isBinaryFile(filename: string, cwd?: string): boolean {
@@ -107,15 +107,15 @@ export interface ReadFileInfoResult extends FileInfo {
107107

108108
export function resolveFilename(filename: string, cwd?: string): string {
109109
cwd = cwd || process.cwd();
110-
if (filename === STDIN) return STDINProtocol;
111-
if (filename.startsWith(FileProtocol)) {
112-
const url = new URL(filename.slice(FileProtocol.length), pathToFileURL(cwd + path.sep));
110+
if (filename === STDIN) return STDINUrlPrefix;
111+
if (filename.startsWith(FileUrlPrefix)) {
112+
const url = new URL(filename.slice(FileUrlPrefix.length), pathToFileURL(cwd + path.sep));
113113
return fileURLToPath(url);
114114
}
115-
const scheme = filename.startsWith(STDINProtocol) ? STDINProtocol : '';
116-
const pathname = filename.slice(scheme.length);
117-
118-
return scheme + path.resolve(cwd, pathname);
115+
if (isStdinUrl(filename)) {
116+
return resolveStdinUrl(filename, cwd);
117+
}
118+
return path.resolve(cwd, filename);
119119
}
120120

121121
export function readFileInfo(
@@ -149,9 +149,7 @@ export function readFile(filename: string, encoding: BufferEncoding = UTF8): Pro
149149
export async function findFiles(globPatterns: string[], options: GlobOptions): Promise<string[]> {
150150
const stdin: string[] = [];
151151
const globPats = globPatterns.filter((filename) =>
152-
filename !== STDIN && !filename.startsWith(STDINProtocol) && !filename.startsWith(FileProtocol)
153-
? true
154-
: (stdin.push(filename), false),
152+
!isStdin(filename) && !filename.startsWith(FileUrlPrefix) ? true : (stdin.push(filename), false),
155153
);
156154
const globResults = globPats.length ? await globP(globPats, options) : [];
157155
const cwd = options.cwd || process.cwd();
@@ -205,7 +203,12 @@ export async function readFileListFile(listFile: string): Promise<string[]> {
205203
}
206204
}
207205

206+
function isStdin(filename: string): boolean {
207+
return filename === STDIN || isStdinUrl(filename);
208+
}
209+
208210
export async function isFile(filename: string): Promise<boolean> {
211+
if (isStdin(filename)) return true;
209212
try {
210213
const stat = await fsp.stat(filename);
211214
return stat.isFile();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import Path from 'node:path';
2+
import { fileURLToPath } from 'node:url';
3+
4+
import { describe, expect, test } from 'vitest';
5+
6+
import { isStdinUrl, resolveStdinUrl } from './stdinUrl.js';
7+
8+
const filenameURL = new URL(import.meta.url);
9+
const __filename = fileURLToPath(import.meta.url);
10+
const __dirname = Path.dirname(__filename);
11+
const dirUrl = new URL('./', import.meta.url);
12+
const stdinUrl = dirUrl.href.replace(/^file:/, 'stdin:');
13+
14+
describe('stdinUrl', () => {
15+
test('isStdinUrl', () => {
16+
expect(isStdinUrl('stdin://')).toBe(true);
17+
expect(isStdinUrl('stdin:')).toBe(true);
18+
expect(isStdinUrl('stdin://foo')).toBe(true);
19+
});
20+
21+
test.each`
22+
url | expected
23+
${'stdin://'} | ${stdinUrl}
24+
${'stdin:package.json'} | ${new URL('package.json', stdinUrl).href}
25+
${'stdin:./package.json'} | ${new URL('package.json', stdinUrl).href}
26+
${'stdin:' + __filename} | ${new URL(filenameURL.pathname, stdinUrl).href}
27+
${'stdin://' + __filename} | ${new URL(filenameURL.pathname, stdinUrl).href}
28+
`('resolveStdinUrl', ({ url, expected }) => {
29+
expect(resolveStdinUrl(url, __dirname)).toBe(expected);
30+
});
31+
});
+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import assert from 'node:assert';
2+
import Path from 'node:path';
3+
import { pathToFileURL } from 'node:url';
4+
5+
import { STDINProtocol } from './constants.js';
6+
7+
export function isStdinUrl(url: string | URL): boolean {
8+
if (url instanceof URL) {
9+
return url.protocol === STDINProtocol;
10+
}
11+
return url.startsWith(STDINProtocol);
12+
}
13+
14+
/**
15+
* Normalize and resolve a stdin url.
16+
* @param url - stdin url to resolve.
17+
* @param cwd - file path to resolve relative paths against.
18+
* @returns
19+
*/
20+
export function resolveStdinUrl(url: string, cwd: string): string {
21+
assert(url.startsWith(STDINProtocol), `Expected url to start with ${STDINProtocol}`);
22+
const path = url
23+
.slice(STDINProtocol.length)
24+
.replace(/^\/\//, '')
25+
.replace(/^\/([a-z]:)/i, '$1');
26+
const fileUrl = pathToFileURL(Path.resolve(cwd, path));
27+
return fileUrl.toString().replace(/^file:/, STDINProtocol) + (path ? '' : '/');
28+
}

0 commit comments

Comments
 (0)