Skip to content

Commit

Permalink
fix: Remove references to fsPath (#3306)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jason3S authored May 23, 2024
1 parent 44fc1e1 commit 0145f10
Show file tree
Hide file tree
Showing 8 changed files with 142 additions and 40 deletions.
5 changes: 5 additions & 0 deletions packages/__utils/src/uriHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,8 @@ function splitUri(uri: Uri) {
.split('/')
.filter((a) => !!a);
}

export function uriToFilePathOrHref(url: Uri | URL | string): string {
const uri = Uri.isUri(url) ? url : toUri(url.toString());
return uri.scheme === 'file' ? uri.fsPath : uri.toString();
}
3 changes: 2 additions & 1 deletion packages/_server/src/config/WorkspacePathResolver.mts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { logError } from '@internal/common-utils/log';
import { uriToFilePathOrHref } from '@internal/common-utils/uriHelper';
import type { BaseSetting, Glob, GlobDef } from 'cspell-lib';
import * as os from 'os';
import * as Path from 'path';
Expand Down Expand Up @@ -72,7 +73,7 @@ function toFolderPath(w: WorkspaceFolder): FolderPath {
const uri = Uri.parse(w.uri);
return {
name: w.name,
path: uri.fsPath,
path: uriToFilePathOrHref(uri),
uri: uri,
};
}
Expand Down
4 changes: 2 additions & 2 deletions packages/_server/src/config/customDictionaries.mts
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,8 @@ function normalizeDictionaryDefinition(def: DictionaryDefinition): NormalizedDic
return isInlineDict(def) ? undefined : def;
}
const { file, path, ...rest } = def;
const fsPath = [path || '', file || ''].filter((a) => !!a).join('/');
const nDef: NormalizedDictionaryDefinition = { ...rest, path: fsPath, file: undefined };
const usePath = [path || '', file || ''].filter((a) => !!a).join('/');
const nDef: NormalizedDictionaryDefinition = { ...rest, path: usePath, file: undefined };
nDef.addWords = canAddWordsToDictionary(nDef);
return nDef;
}
Expand Down
30 changes: 14 additions & 16 deletions packages/_server/src/config/documentSettings.mts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import { createEmitter, setIfDefined } from '@internal/common-utils';
import type { AutoLoadCache, LazyValue } from '@internal/common-utils/autoLoad';
import { createAutoLoadCache, createLazyValue } from '@internal/common-utils/autoLoad';
import { log } from '@internal/common-utils/log';
import { toFileUri, toUri } from '@internal/common-utils/uriHelper';
import { findRepoRoot, GitIgnore } from 'cspell-gitignore';
import { toFileUri, toUri, uriToFilePathOrHref } from '@internal/common-utils/uriHelper';
import { GitIgnore } from 'cspell-gitignore';
import type { GlobMatchOptions, GlobMatchRule, GlobPatternNormalized } from 'cspell-glob';
import { GlobMatcher } from 'cspell-glob';
import type { ExcludeFilesGlobMap } from 'cspell-lib';
Expand All @@ -39,7 +39,7 @@ import type { DocumentUri, ServerSideApi, VSCodeSettingsCspell, WorkspaceConfigF
import { extensionId } from '../constants.mjs';
import { uniqueFilter } from '../utils/index.mjs';
import { findMatchingFoldersForUri } from '../utils/matchingFoldersForUri.mjs';
import { stat } from '../vfs/index.mjs';
import { findRepoRoot, stat } from '../vfs/index.mjs';
import type { VSConfigAdvanced } from './cspellConfig/cspellConfig.mjs';
import { filterMergeFields } from './cspellConfig/cspellMergeFields.mjs';
import type { EnabledSchemes } from './cspellConfig/FileTypesAndSchemeSettings.mjs';
Expand All @@ -58,7 +58,6 @@ const cSpellSection: keyof SettingsCspell = extensionId;

const configKeyTrustedWorkspace = 'cSpell.trustedWorkspace' as const satisfies keyof VSConfigAdvanced;

type FsPath = string;
export interface SettingsVSCode {
search?: {
exclude?: ExcludeFilesGlobMap;
Expand Down Expand Up @@ -122,7 +121,7 @@ export class DocumentSettings {
private valuesToClearOnReset: ClearFn[] = [];
private readonly fetchSettingsForUri = this.createCache((docUri: string | undefined) => this._fetchSettingsForUri(docUri));
private readonly fetchVSCodeConfiguration = this.createCache((uri?: string) => this._fetchVSCodeConfiguration(uri));
private readonly fetchRepoRootForDir = this.createCache((dir: FsPath) => findRepoRoot(dir));
private readonly fetchRepoRootForDir = this.createCache((dir: Uri) => findRepoRoot(dir));
private readonly pendingUrisToRelease = new Map<string, NodeJS.Timeout>();
public readonly fetchWorkspaceConfiguration = this.createCache((docUri: DocumentUri) => this._fetchWorkspaceConfiguration(docUri));
private readonly _folders = this.createLazy(() => this.fetchFolders());
Expand Down Expand Up @@ -210,7 +209,7 @@ export class DocumentSettings {
*/
private async _isGitIgnored(extSettings: ExtSettings, uri: Uri): Promise<boolean | undefined> {
if (!extSettings.settings.useGitignore) return undefined;
return await this.gitIgnore.isIgnored(uri.fsPath);
return await this.gitIgnore.isIgnored(uriToFilePathOrHref(uri));
}

/**
Expand All @@ -225,9 +224,9 @@ export class DocumentSettings {
if (!extSettings.settings.useGitignore) return undefined;
const root = await this.fetchRepoRootForFile(uri);
if (root) {
this.gitIgnore.addRoots([root]);
this.gitIgnore.addRoots([uriToFilePathOrHref(root)]);
}
return await this.gitIgnore.isIgnoredEx(uri.fsPath);
return await this.gitIgnore.isIgnoredEx(uriToFilePathOrHref(uri));
}

async calcExcludedBy(uri: string): Promise<ExcludedByMatch[]> {
Expand Down Expand Up @@ -342,7 +341,7 @@ export class DocumentSettings {
private async fetchRepoRootForFile(uriFile: string | Uri) {
const u = toUri(uriFile);
const uriDir = UriUtils.dirname(u);
return this.fetchRepoRootForDir(uriDir.fsPath);
return this.fetchRepoRootForDir(uriDir);
}

public async findCSpellConfigurationFilesForUri(docUri: string | Uri): Promise<Uri[]> {
Expand Down Expand Up @@ -437,7 +436,6 @@ export class DocumentSettings {
// console.error('fetchSettingsForUri: %o', { fileVSCodeSettings, useUriForConfig });
const useURLForConfig = new URL(fileVSCodeSettings, useUriForConfig);
const searchForUri = Uri.parse(useUriForConfig);
const searchForFsPath = path.normalize(searchForUri.fsPath);
const vscodeCSpellConfigSettingsRel = await this.fetchSettingsFromVSCode(docUri);
const enabledFileTypes = extractEnabledFileTypes(vscodeCSpellConfigSettingsRel);
const enabledSchemes = extractEnabledSchemes(vscodeCSpellConfigSettingsRel);
Expand Down Expand Up @@ -465,15 +463,15 @@ export class DocumentSettings {
settings,
);

let fileSettings: CSpellUserSettings = calcOverrideSettings(mergedSettings, searchForFsPath);
let fileSettings: CSpellUserSettings = calcOverrideSettings(mergedSettings, uriToFilePathOrHref(searchForUri));
fileSettings = applyEnabledFileTypes(fileSettings, enabledFileTypes);
fileSettings = applyEnabledSchemes(fileSettings, enabledSchemes);
const { ignorePaths = [], files = [] } = fileSettings;

const globRoot = Uri.parse(globRootFolder.uri).fsPath;
const globRoot = uriToFilePathOrHref(globRootFolder.uri);
if (!files.length && vscodeCSpellConfigSettingsForDocument.spellCheckOnlyWorkspaceFiles !== false) {
// Add file globs that will match the entire workspace.
folders.forEach((folder) => files.push({ glob: '/**', root: Uri.parse(folder.uri).fsPath }));
folders.forEach((folder) => files.push({ glob: '/**', root: uriToFilePathOrHref(folder.uri) }));
fileSettings.enableGlobDot = fileSettings.enableGlobDot ?? true;
}
fileSettings.files = files;
Expand Down Expand Up @@ -646,7 +644,7 @@ export interface ExcludedByMatch {
}

function calcExcludedBy(uri: string, extSettings: ExtSettings): ExcludedByMatch[] {
const filename = path.normalize(Uri.parse(uri).fsPath);
const filename = uriToFilePathOrHref(uri);
const matchResult = extSettings.excludeGlobMatcher.matchEx(filename);

if (matchResult.matched === false) {
Expand Down Expand Up @@ -792,11 +790,11 @@ export function calcIncludeExclude(settings: ExtSettings, uri: Uri): { include:

export function isIncluded(settings: ExtSettings, uri: Uri): boolean {
const files = settings.settings.files;
return !files?.length || settings.includeGlobMatcher.match(uri.fsPath);
return !files?.length || settings.includeGlobMatcher.match(uriToFilePathOrHref(uri));
}

export function isExcluded(settings: ExtSettings, uri: Uri): boolean {
return settings.excludeGlobMatcher.match(uri.fsPath);
return settings.excludeGlobMatcher.match(uriToFilePathOrHref(uri));
}

async function filterUrl(uri: Uri): Promise<Uri | undefined> {
Expand Down
2 changes: 1 addition & 1 deletion packages/_server/src/vfs/index.mts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export { readTextDocument } from './readTextDocument.mjs';
export { isDir, isFile, readTextFile, stat } from './vfs.mjs';
export { findRepoRoot, findUp, isDir, isFile, normalizeDirUrl, readTextFile, stat, statIfAvailable } from './vfs.mjs';
96 changes: 78 additions & 18 deletions packages/_server/src/vfs/vfs.mts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import assert from 'node:assert';

import type { VfsStat } from 'cspell-io';
import { getVirtualFS } from 'cspell-lib';
import type { URI } from 'vscode-uri';
Expand All @@ -12,6 +14,19 @@ export async function stat(urlLike: URL | URI | string): Promise<VfsStat> {
return stat;
}

/**
* Get the file stat if it is available, otherwise return undefined.
* @param urlLike - url
* @returns VfsStat or undefined
*/
export async function statIfAvailable(urlLike: URL | URI | string): Promise<VfsStat | undefined> {
try {
return await stat(urlLike);
} catch {
return undefined;
}
}

export async function readTextFile(url: URL | URI | string): Promise<string> {
const _url = toUrl(url);
const vfs = getVirtualFS();
Expand All @@ -21,28 +36,73 @@ export async function readTextFile(url: URL | URI | string): Promise<string> {
}

export async function isFile(url: URL | URI | string): Promise<boolean> {
try {
const statInfo = await stat(url);
return statInfo.isFile();
} catch {
return false;
}
const statInfo = await statIfAvailable(url);
return statInfo?.isFile() || false;
}

export async function isDir(url: URL | URI | string): Promise<boolean> {
try {
const statInfo = await stat(url);
return statInfo.isDirectory();
} catch {
return false;
}
const statInfo = await statIfAvailable(url);
return statInfo?.isDirectory() || false;
}

export async function exists(url: URL | URI | string): Promise<boolean> {
try {
await stat(url);
return true;
} catch {
return false;
}
return !!(await statIfAvailable(url));
}

export interface FindUpOptions {
cwd: URL | string;
root?: URL | string | undefined;
predicate?: (url: URL, stat: VfsStat) => boolean;
}

/**
* Find a file or directory by name in the current directory or any parent directory.
* @param name - name of the file/directory to find
* @param options - options for findUp
*/
export async function findUp(name: string, options: FindUpOptions): Promise<URL | undefined> {
let cwd = new URL(options.cwd);
const root = new URL('.', options.root || new URL('/', cwd));
const predicate = options.predicate || (() => true);
assert(cwd.toString().startsWith(root.toString()), 'cwd must be a subdirectory of root');
let last = cwd;
do {
last = cwd;
const url = new URL(name, cwd);
const stat = await statIfAvailable(url);
if (stat && predicate(url, stat)) {
return url;
}
cwd = new URL('..', cwd);
} while (cwd.pathname !== root.pathname && last.pathname !== cwd.pathname);
return undefined;
}

/**
* Normalize a directory URL by ensuring it ends with a `/`. If it already has a `/`, it is returned as is.
* Otherwise, if the URL points to a directory, the URL is returned with a `/` at the end.
* @param url - url to normalize
* @returns URL with a `/` at the end.
*/
export async function normalizeDirUrl(url: URL | URI | string): Promise<URL> {
const u = toUrl(url);
if (u.pathname.endsWith('/')) return u;

const s = await stat(u);
if (s.isDirectory()) return new URL(u.pathname + '/', u);
return new URL('.', u);
}

export interface FindRepoRootOptions {
root?: URL | string | undefined;
}

/**
* Look for the root of a git repository.
* @param url - url to find the repo root for.
* @returns Resolves to URL or undefined
*/
export async function findRepoRoot(url: URL | URI | string, options?: FindRepoRootOptions): Promise<URL | undefined> {
const found = await findUp('.git/', { ...options, cwd: toUrl(url), predicate: (_url, stat) => stat.isDirectory() });
return found ? new URL('..', found) : undefined;
}
36 changes: 36 additions & 0 deletions packages/_server/src/vfs/vfs.test.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import type { VfsStat } from 'cspell-io';
import { describe, expect, test } from 'vitest';

import { findRepoRoot, findUp, normalizeDirUrl } from './vfs.mjs';

const packageRoot = new URL('../../', import.meta.url);
const repoRoot = new URL('../../', packageRoot);
const basename = import.meta.url.split('/').slice(-1).join('');

describe('vfs', () => {
test.each`
name | options | expected
${basename} | ${{ cwd: import.meta.url }} | ${import.meta.url}
${'package.json'} | ${{ cwd: import.meta.url }} | ${new URL('package.json', packageRoot).toString()}
${'package.json'} | ${{ cwd: import.meta.url, predicate: () => false }} | ${undefined}
${'.git'} | ${{ cwd: import.meta.url, predicate: (_: URL, stat: VfsStat) => stat.isDirectory() }} | ${new URL('.git', repoRoot).toString()}
${'.git'} | ${{ cwd: import.meta.url }} | ${new URL('.git', repoRoot).toString()}
${'.git'} | ${{ cwd: import.meta.url, root: packageRoot }} | ${undefined}
`('findUp $name, $options', async ({ name, options, expected }) => {
const result = await findUp(name, options);
expect(result?.toString()).toEqual(expected);
});

test.each`
url | expected
${packageRoot} | ${packageRoot}
${import.meta.url} | ${new URL('.', import.meta.url)}
${import.meta.url.split('/').slice(0, -1).join('/')} | ${new URL('.', import.meta.url)}
`('normalizeDirUrl $url', async ({ url, expected }) => {
expect((await normalizeDirUrl(url)).toString()).toEqual(expected.toString());
});

test('findRepoRoot', async () => {
expect((await findRepoRoot(import.meta.url))?.toString()).toEqual(repoRoot.toString());
});
});
6 changes: 4 additions & 2 deletions packages/client/src/settings/DictionaryTarget.mts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { isErrnoException } from '@internal/common-utils';
import { uriToName } from '@internal/common-utils/uriHelper';
import { uriToFilePathOrHref, uriToName } from '@internal/common-utils/uriHelper';
import { format } from 'util';
import type { Uri } from 'vscode';
import { window, workspace } from 'vscode';
Expand Down Expand Up @@ -88,7 +88,9 @@ async function addWordsToCustomDictionary(words: string[], dict: CustomDictDef):

async function updateWordInCustomDictionary(updateFn: (words: string[]) => string[], dict: CustomDictDef): Promise<void> {
if (regBlockUpdateDictionaryFormat.test(dict.uri.path)) {
return Promise.reject(new Error(`Failed to add words to dictionary "${dict.name}", unsupported format: "${dict.uri.fsPath}".`));
return Promise.reject(
new Error(`Failed to add words to dictionary "${dict.name}", unsupported format: "${uriToFilePathOrHref(dict.uri)}".`),
);
}
try {
await ensureFileExists(dict.uri);
Expand Down

0 comments on commit 0145f10

Please sign in to comment.