From 28fcc5c618e82fdfaec1983516ab80c4bb4fae91 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 10 Feb 2021 15:58:09 -0700 Subject: [PATCH 1/3] Move generic FS utils to the right module. --- src/client/common/platform/fileSystem.ts | 25 +------- src/client/common/platform/types.ts | 6 +- src/client/common/utils/filesystem.ts | 59 +++++++++++++++++++ .../pythonEnvironments/common/commonUtils.ts | 25 +------- 4 files changed, 62 insertions(+), 53 deletions(-) diff --git a/src/client/common/platform/fileSystem.ts b/src/client/common/platform/fileSystem.ts index c3e8bcc025fa..5a61835494d4 100644 --- a/src/client/common/platform/fileSystem.ts +++ b/src/client/common/platform/fileSystem.ts @@ -12,6 +12,7 @@ import { promisify } from 'util'; import * as vscode from 'vscode'; import '../extensions'; import { traceError } from '../logger'; +import { convertFileType } from '../utils/filesystem'; import { createDirNotEmptyError, isFileExistsError, isFileNotFoundError, isNoPermissionsError } from './errors'; import { FileSystemPaths, FileSystemPathUtils } from './fs-paths'; import { TemporaryFileSystem } from './fs-temp'; @@ -31,30 +32,6 @@ import { const ENCODING = 'utf8'; -interface IKnowsFileType { - isFile(): boolean; - isDirectory(): boolean; - isSymbolicLink(): boolean; -} - -// This helper function determines the file type of the given stats -// object. The type follows the convention of node's fs module, where -// a file has exactly one type. Symlinks are not resolved. -export function convertFileType(info: IKnowsFileType): FileType { - if (info.isFile()) { - return FileType.File; - } - if (info.isDirectory()) { - return FileType.Directory; - } - if (info.isSymbolicLink()) { - // The caller is responsible for combining this ("logical or") - // with File or Directory as necessary. - return FileType.SymbolicLink; - } - return FileType.Unknown; -} - export function convertStat(old: fs.Stats, filetype: FileType): FileStat { return { type: filetype, diff --git a/src/client/common/platform/types.ts b/src/client/common/platform/types.ts index 507cbd1ee3d1..5706b3044453 100644 --- a/src/client/common/platform/types.ts +++ b/src/client/common/platform/types.ts @@ -90,16 +90,12 @@ export interface IFileSystemPathUtils { //=========================== // filesystem operations +// We could use FileType from utils/filesystem.ts, but it's simpler this way. export import FileType = vscode.FileType; export import FileStat = vscode.FileStat; export type ReadStream = fs.ReadStream; export type WriteStream = fs.WriteStream; -export type DirEntry = { - filename: string; - filetype: FileType; -}; - // The low-level filesystem operations on which the extension depends. export interface IRawFileSystem { // Get information about a file (resolve symlinks). diff --git a/src/client/common/utils/filesystem.ts b/src/client/common/utils/filesystem.ts index 84dd9b814be0..91b6b76b7d4c 100644 --- a/src/client/common/utils/filesystem.ts +++ b/src/client/common/utils/filesystem.ts @@ -1,7 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +import * as fs from 'fs'; import * as path from 'path'; +import * as vscode from 'vscode'; +import { logError } from '../../logging'; import { getOSType, OSType } from './platform'; /** @@ -28,3 +31,59 @@ export function areSameFilename(filename1: string, filename2: string): boolean { const norm2 = normalizeFilename(filename2); return norm1 === norm2; } + +export import FileType = vscode.FileType; + +export type DirEntry = { + filename: string; + filetype: FileType; +}; + +interface IKnowsFileType { + isFile(): boolean; + isDirectory(): boolean; + isSymbolicLink(): boolean; +} + +// This helper function determines the file type of the given stats +// object. The type follows the convention of node's fs module, where +// a file has exactly one type. Symlinks are not resolved. +export function convertFileType(info: IKnowsFileType): FileType { + if (info.isFile()) { + return FileType.File; + } + if (info.isDirectory()) { + return FileType.Directory; + } + if (info.isSymbolicLink()) { + // The caller is responsible for combining this ("logical or") + // with File or Directory as necessary. + return FileType.SymbolicLink; + } + return FileType.Unknown; +} + +/** + * Identify the file type for the given file. + */ +export async function getFileType( + filename: string, + opts: { + ignoreErrors: boolean; + } = { ignoreErrors: true }, +): Promise { + let stat: fs.Stats; + try { + stat = await fs.promises.lstat(filename); + } catch (err) { + if (err.code === 'ENOENT') { + return undefined; + } + if (opts.ignoreErrors) { + logError(`lstat() failed for "${filename}" (${err})`); + return FileType.Unknown; + } + throw err; // re-throw + } + return convertFileType(stat); +} diff --git a/src/client/pythonEnvironments/common/commonUtils.ts b/src/client/pythonEnvironments/common/commonUtils.ts index 95467aff687e..9c4e5775fc0f 100644 --- a/src/client/pythonEnvironments/common/commonUtils.ts +++ b/src/client/pythonEnvironments/common/commonUtils.ts @@ -3,8 +3,7 @@ import * as fs from 'fs'; import * as path from 'path'; -import { convertFileType } from '../../common/platform/fileSystem'; -import { DirEntry, FileType } from '../../common/platform/types'; +import { convertFileType, DirEntry, FileType, getFileType } from '../../common/utils/filesystem'; import { getOSType, OSType } from '../../common/utils/platform'; import { logError } from '../../logging'; import { PythonVersion, UNKNOWN_PYTHON_VERSION } from '../base/info'; @@ -172,28 +171,6 @@ async function readDirEntries( return entries; } -async function getFileType( - filename: string, - opts: { - ignoreErrors: boolean; - } = { ignoreErrors: true }, -): Promise { - let stat: fs.Stats; - try { - stat = await fs.promises.lstat(filename); - } catch (err) { - if (err.code === 'ENOENT') { - return undefined; - } - if (opts.ignoreErrors) { - logError(`lstat() failed for "${filename}" (${err})`); - return FileType.Unknown; - } - throw err; // re-throw - } - return convertFileType(stat); -} - function matchFile( filename: string, filterFile: FileFilterFunc | undefined, From 9aa1dac2a5355ce195d47b8799046ed707c20d45 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 10 Feb 2021 17:22:43 -0700 Subject: [PATCH 2/3] Add getFileFilter(). --- src/client/common/utils/filesystem.ts | 80 +++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/src/client/common/utils/filesystem.ts b/src/client/common/utils/filesystem.ts index 91b6b76b7d4c..d2af70a1c324 100644 --- a/src/client/common/utils/filesystem.ts +++ b/src/client/common/utils/filesystem.ts @@ -87,3 +87,83 @@ export async function getFileType( } return convertFileType(stat); } + +function normalizeFileTypes(filetypes: FileType | FileType[] | undefined): FileType[] | undefined { + if (filetypes === undefined) { + return undefined; + } + if (Array.isArray(filetypes)) { + if (filetypes.length === 0) { + return undefined; + } + return filetypes; + } + return [filetypes]; +} + +async function resolveFile( + file: string | DirEntry, + opts: { + ensure?: boolean; + onMissing?: FileType; + } = {}, +): Promise { + let filename: string; + if (typeof file !== 'string') { + if (!opts.ensure) { + if (opts.onMissing === undefined) { + return file; + } + // At least make sure it exists. + if ((await getFileType(file.filename)) !== undefined) { + return file; + } + } + filename = file.filename; + } else { + filename = file; + } + + const filetype = (await getFileType(filename)) || opts.onMissing; + if (filetype === undefined) { + return undefined; + } + return { filename, filetype }; +} + +type FileFilterFunc = (file: string | DirEntry) => Promise; + +export function getFileFilter( + opts: { + ignoreMissing?: boolean; + ignoreFileType?: FileType | FileType[]; + ensureEntry?: boolean; + } = { + ignoreMissing: true, + }, +): FileFilterFunc | undefined { + const ignoreFileType = normalizeFileTypes(opts.ignoreFileType); + + if (!opts.ignoreMissing && !ignoreFileType) { + // Do not filter. + return undefined; + } + + async function filterFile(file: string | DirEntry): Promise { + let entry = await resolveFile(file, { ensure: opts.ensureEntry }); + if (!entry) { + if (opts.ignoreMissing) { + return false; + } + const filename = typeof file === 'string' ? file : file.filename; + entry = { filename, filetype: FileType.Unknown }; + } + if (ignoreFileType) { + if (ignoreFileType.includes(entry!.filetype)) { + return false; + } + } + return true; + } + return filterFile; +} From b6de38c904991f0e0038de150189525bd061d2a4 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 16 Feb 2021 17:20:46 -0700 Subject: [PATCH 3/3] eslint --- .eslintignore | 1 - src/client/common/platform/fileSystem.ts | 2 +- src/client/common/platform/types.ts | 24 ++++++++++++------------ 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/.eslintignore b/.eslintignore index 361325549a6b..4f9d559928f6 100644 --- a/.eslintignore +++ b/.eslintignore @@ -513,7 +513,6 @@ src/client/common/platform/errors.ts src/client/common/platform/fs-temp.ts src/client/common/platform/fs-paths.ts src/client/common/platform/platformService.ts -src/client/common/platform/types.ts src/client/common/platform/registry.ts src/client/common/platform/pathUtils.ts src/client/common/persistentState.ts diff --git a/src/client/common/platform/fileSystem.ts b/src/client/common/platform/fileSystem.ts index 5a61835494d4..d2db7a721cb6 100644 --- a/src/client/common/platform/fileSystem.ts +++ b/src/client/common/platform/fileSystem.ts @@ -527,7 +527,7 @@ export class FileSystem implements IFileSystem { } // eslint-disable-next-line @typescript-eslint/ban-types - public async writeFile(filename: string, data: {}): Promise { + public async writeFile(filename: string, data: string | Buffer): Promise { return this.utils.raw.writeText(filename, data); } diff --git a/src/client/common/platform/types.ts b/src/client/common/platform/types.ts index 5706b3044453..1d4f63948a10 100644 --- a/src/client/common/platform/types.ts +++ b/src/client/common/platform/types.ts @@ -7,7 +7,7 @@ import { SemVer } from 'semver'; import * as vscode from 'vscode'; import { Architecture, OSType } from '../utils/platform'; -//=========================== +//= ========================== // registry export enum RegistryHive { @@ -21,7 +21,7 @@ export interface IRegistry { getValue(key: string, hive: RegistryHive, arch?: Architecture, name?: string): Promise; } -//=========================== +//= ========================== // platform export const IsWindows = Symbol('IS_WINDOWS'); @@ -41,7 +41,7 @@ export interface IPlatformService { getVersion(): Promise; } -//=========================== +//= ========================== // temp FS export type TemporaryFile = { filePath: string } & vscode.Disposable; @@ -51,7 +51,7 @@ export interface ITempFileSystem { createFile(suffix: string, mode?: number): Promise; } -//=========================== +//= ========================== // FS paths // The low-level file path operations used by the extension. @@ -87,7 +87,7 @@ export interface IFileSystemPathUtils { getDisplayName(pathValue: string, cwd?: string): string; } -//=========================== +//= ========================== // filesystem operations // We could use FileType from utils/filesystem.ts, but it's simpler this way. @@ -107,7 +107,7 @@ export interface IRawFileSystem { // Move the file to a different location (and/or rename it). move(src: string, tgt: string): Promise; - //*********************** + //* ********************** // files // Return the raw bytes of the given file. @@ -115,7 +115,7 @@ export interface IRawFileSystem { // Return the text of the given file (decoded from UTF-8). readText(filename: string): Promise; // Write the given text to the file (UTF-8 encoded). - writeText(filename: string, data: {}): Promise; + writeText(filename: string, data: string | Buffer): Promise; // Write the given text to the end of the file (UTF-8 encoded). appendText(filename: string, text: string): Promise; // Copy a file. @@ -123,7 +123,7 @@ export interface IRawFileSystem { // Delete a file. rmfile(filename: string): Promise; - //*********************** + //* ********************** // directories // Create the directory and any missing parent directories. @@ -135,7 +135,7 @@ export interface IRawFileSystem { // Return the contents of the directory. listdir(dirname: string): Promise<[string, FileType][]>; - //*********************** + //* ********************** // not async // Get information about a file (resolve symlinks). @@ -155,14 +155,14 @@ export interface IFileSystemUtils { readonly pathUtils: IFileSystemPathUtils; readonly tmp: ITempFileSystem; - //*********************** + //* ********************** // aliases createDirectory(dirname: string): Promise; deleteDirectory(dirname: string): Promise; deleteFile(filename: string): Promise; - //*********************** + //* ********************** // helpers // Determine if the file exists, optionally requiring the type. @@ -184,7 +184,7 @@ export interface IFileSystemUtils { // Get the paths of all files matching the pattern. search(globPattern: string): Promise; - //*********************** + //* ********************** // helpers (non-async) fileExistsSync(path: string): boolean;