forked from DonJayamanne/pythonVSCode
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Factor out path-related classes for better testing. #9352
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
ericsnowcurrently
merged 15 commits into
microsoft:master
from
ericsnowcurrently:fs-paths
Jan 8, 2020
Merged
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
e7d4ee0
Factor out IFileSystemPathUtils and friends.
ericsnowcurrently 1e05c64
Add missing functional tests.
ericsnowcurrently 3ab7ec7
Add FileSystemPaths.normalize().
ericsnowcurrently b83f20f
Add FileSystemPaths.join().
ericsnowcurrently 7c4c44a
Use FileSystemPaths in the FileSystem class.
ericsnowcurrently a390c45
Add FileSystemPaths.normCase().
ericsnowcurrently 806fc17
Add FileSystemPaths.dirname().
ericsnowcurrently 1634f92
Move arePathsSame() over to FileSystemPathUtils.
ericsnowcurrently ff554d1
Move getRealPath() to FileSystemPathUtils.
ericsnowcurrently e5fa475
Clean up pathutils tests.
ericsnowcurrently 47b1ec0
Drop some dead code.
ericsnowcurrently eefc371
Ensure full coverage on FileSystem.
ericsnowcurrently 3e1d52f
Add missing doc comments.
ericsnowcurrently 231968b
Address/ignore prettier failures.
ericsnowcurrently f589160
Skip a test on Windows.
ericsnowcurrently File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,9 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
// TO DO: Deprecate in favor of IPlatformService | ||
// tslint:disable-next-line:no-suspicious-comment | ||
// TODO (GH-8542): Drop all these in favor of IPlatformService | ||
|
||
export const WINDOWS_PATH_VARIABLE_NAME = 'Path'; | ||
export const NON_WINDOWS_PATH_VARIABLE_NAME = 'PATH'; | ||
export const IS_WINDOWS = /^win/.test(process.platform); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
import * as fs from 'fs-extra'; | ||
import * as nodepath from 'path'; | ||
import { getOSType, OSType } from '../utils/platform'; | ||
// prettier-ignore | ||
import { | ||
IExecutables, | ||
IFileSystemPaths | ||
} from './types'; | ||
// tslint:disable-next-line:no-var-requires no-require-imports | ||
const untildify = require('untildify'); | ||
|
||
// The parts of node's 'path' module used by FileSystemPaths. | ||
interface INodePath { | ||
sep: string; | ||
join(...filenames: string[]): string; | ||
dirname(filename: string): string; | ||
basename(filename: string, ext?: string): string; | ||
normalize(filename: string): string; | ||
} | ||
|
||
// The file path operations used by the extension. | ||
export class FileSystemPaths { | ||
// prettier-ignore | ||
constructor( | ||
private readonly isCaseInsensitive: boolean, | ||
private readonly raw: INodePath | ||
) { } | ||
// Create a new object using common-case default values. | ||
// We do not use an alternate constructor because defaults in the | ||
// constructor runs counter to our typical approach. | ||
// prettier-ignore | ||
public static withDefaults( | ||
isCaseInsensitive?: boolean | ||
): FileSystemPaths { | ||
if (isCaseInsensitive === undefined) { | ||
isCaseInsensitive = getOSType() === OSType.Windows; | ||
} | ||
// prettier-ignore | ||
return new FileSystemPaths( | ||
isCaseInsensitive, | ||
nodepath | ||
); | ||
} | ||
|
||
public get sep(): string { | ||
return this.raw.sep; | ||
} | ||
|
||
public join(...filenames: string[]): string { | ||
return this.raw.join(...filenames); | ||
} | ||
|
||
public dirname(filename: string): string { | ||
return this.raw.dirname(filename); | ||
} | ||
|
||
public basename(filename: string, suffix?: string): string { | ||
return this.raw.basename(filename, suffix); | ||
} | ||
|
||
public normalize(filename: string): string { | ||
return this.raw.normalize(filename); | ||
} | ||
|
||
public normCase(filename: string): string { | ||
filename = this.raw.normalize(filename); | ||
// prettier-ignore | ||
return this.isCaseInsensitive | ||
? filename.toUpperCase() | ||
: filename; | ||
} | ||
} | ||
|
||
// Where to fine executables. | ||
// | ||
// In particular this class provides all the tools needed to find | ||
// executables, including through an environment variable. | ||
export class Executables { | ||
// prettier-ignore | ||
constructor( | ||
public readonly delimiter: string, | ||
private readonly osType: OSType | ||
) { } | ||
// Create a new object using common-case default values. | ||
// We do not use an alternate constructor because defaults in the | ||
// constructor runs counter to our typical approach. | ||
public static withDefaults(): Executables { | ||
// prettier-ignore | ||
return new Executables( | ||
nodepath.delimiter, | ||
getOSType() | ||
); | ||
} | ||
|
||
public get envVar(): string { | ||
// prettier-ignore | ||
return this.osType === OSType.Windows | ||
? 'Path' | ||
: 'PATH'; | ||
} | ||
} | ||
|
||
// The dependencies FileSystemPathUtils has on node's path module. | ||
interface IRawPaths { | ||
relative(relpath: string, rootpath: string): string; | ||
} | ||
|
||
// A collection of high-level utilities related to filesystem paths. | ||
export class FileSystemPathUtils { | ||
// prettier-ignore | ||
constructor( | ||
public readonly home: string, | ||
public readonly paths: IFileSystemPaths, | ||
public readonly executables: IExecutables, | ||
private readonly raw: IRawPaths | ||
) { } | ||
// Create a new object using common-case default values. | ||
// We do not use an alternate constructor because defaults in the | ||
// constructor runs counter to our typical approach. | ||
// prettier-ignore | ||
public static withDefaults( | ||
paths?: IFileSystemPaths | ||
): FileSystemPathUtils { | ||
if (paths === undefined) { | ||
paths = FileSystemPaths.withDefaults(); | ||
} | ||
// prettier-ignore | ||
return new FileSystemPathUtils( | ||
untildify('~'), | ||
paths, | ||
Executables.withDefaults(), | ||
nodepath | ||
); | ||
} | ||
|
||
// Return true if the two paths are equivalent on the current | ||
// filesystem and false otherwise. On Windows this is significant. | ||
// On non-Windows the filenames must always be exactly the same. | ||
public arePathsSame(path1: string, path2: string): boolean { | ||
path1 = this.paths.normCase(path1); | ||
path2 = this.paths.normCase(path2); | ||
return path1 === path2; | ||
} | ||
|
||
// Return the canonicalized absolute filename. | ||
public async getRealPath(filename: string): Promise<string> { | ||
try { | ||
return await fs.realpath(filename); | ||
} catch { | ||
// We ignore the error. | ||
return filename; | ||
} | ||
} | ||
|
||
// Return the clean (displayable) form of the given filename. | ||
public getDisplayName(filename: string, cwd?: string): string { | ||
if (cwd && filename.startsWith(cwd)) { | ||
return `.${this.paths.sep}${this.raw.relative(cwd, filename)}`; | ||
} else if (filename.startsWith(this.home)) { | ||
return `~${this.paths.sep}${this.raw.relative(this.home, filename)}`; | ||
} else { | ||
return filename; | ||
} | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,36 +1,64 @@ | ||
// tslint:disable-next-line:no-suspicious-comment | ||
// TODO(GH-8542): Drop this file. | ||
|
||
import { inject, injectable } from 'inversify'; | ||
import * as path from 'path'; | ||
import { IPathUtils, IsWindows } from '../types'; | ||
import { NON_WINDOWS_PATH_VARIABLE_NAME, WINDOWS_PATH_VARIABLE_NAME } from './constants'; | ||
import { OSType } from '../utils/platform'; | ||
// prettier-ignore | ||
import { | ||
Executables, | ||
FileSystemPaths, | ||
FileSystemPathUtils | ||
} from './fs-paths'; | ||
// tslint:disable-next-line:no-var-requires no-require-imports | ||
const untildify = require('untildify'); | ||
|
||
@injectable() | ||
export class PathUtils implements IPathUtils { | ||
public readonly home = ''; | ||
constructor(@inject(IsWindows) private isWindows: boolean) { | ||
this.home = untildify('~'); | ||
private readonly utils: FileSystemPathUtils; | ||
// prettier-ignore | ||
constructor( | ||
@inject(IsWindows) isWindows: boolean | ||
) { | ||
// We cannot just use FileSystemPathUtils.withDefaults() because | ||
// of the isWindows arg. | ||
// prettier-ignore | ||
this.utils = new FileSystemPathUtils( | ||
untildify('~'), | ||
FileSystemPaths.withDefaults(), | ||
new Executables( | ||
path.delimiter, | ||
isWindows ? OSType.Windows : OSType.Unknown | ||
), | ||
path | ||
); | ||
} | ||
|
||
public get home(): string { | ||
return this.utils.home; | ||
} | ||
|
||
public get delimiter(): string { | ||
return path.delimiter; | ||
return this.utils.executables.delimiter; | ||
} | ||
|
||
public get separator(): string { | ||
return path.sep; | ||
} | ||
// TO DO: Deprecate in favor of IPlatformService | ||
public getPathVariableName() { | ||
return this.isWindows ? WINDOWS_PATH_VARIABLE_NAME : NON_WINDOWS_PATH_VARIABLE_NAME; | ||
return this.utils.paths.sep; | ||
} | ||
public basename(pathValue: string, ext?: string): string { | ||
return path.basename(pathValue, ext); | ||
|
||
// tslint:disable-next-line:no-suspicious-comment | ||
// TODO: Deprecate in favor of IPlatformService? | ||
public getPathVariableName(): 'Path' | 'PATH' { | ||
// tslint:disable-next-line:no-any | ||
return this.utils.executables.envVar as any; | ||
} | ||
|
||
public getDisplayName(pathValue: string, cwd?: string): string { | ||
if (cwd && pathValue.startsWith(cwd)) { | ||
return `.${path.sep}${path.relative(cwd, pathValue)}`; | ||
} else if (pathValue.startsWith(this.home)) { | ||
return `~${path.sep}${path.relative(this.home, pathValue)}`; | ||
} else { | ||
return pathValue; | ||
} | ||
return this.utils.getDisplayName(pathValue, cwd); | ||
} | ||
|
||
public basename(pathValue: string, ext?: string): string { | ||
return this.utils.paths.basename(pathValue, ext); | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.