Replace usages of fs with vscode.workspace.fs (#1005)

* Replace usages of fs with vscode.workspace.fs

* Add no-restricted-imports rule for fs module
This commit is contained in:
Josh Dover
2022-06-08 14:12:03 +02:00
committed by GitHub
parent eb74e57a9e
commit f747d7445a
13 changed files with 90 additions and 56 deletions

View File

@@ -28,6 +28,22 @@
}
]
},
"overrides": [
{
// Restrict usage of fs module outside tests to keep foam compatible with the browser
"files": ["**/src/**"],
"excludedFiles": ["**/src/test/**", "**/src/**/*{test,spec}.ts"],
"rules": {
"no-restricted-imports": [
"error",
{
"name": "fs",
"message": "Extension code must not rely Node.js filesystem, use vscode.workspace.fs instead."
}
]
}
}
],
"settings": {
"import/core-modules": ["vscode"],
"import/parsers": {

View File

@@ -1,5 +1,5 @@
import { generateHeading } from '.';
import { TEST_DATA_DIR } from '../../test/test-utils';
import { readFileFromFs, TEST_DATA_DIR } from '../../test/test-utils';
import { MarkdownResourceProvider } from '../services/markdown-provider';
import { bootstrap } from '../model/foam';
import { Resource } from '../model/note';
@@ -20,8 +20,9 @@ describe('generateHeadings', () => {
beforeAll(async () => {
const matcher = new Matcher([TEST_DATA_DIR.joinPath('__scaffold__')]);
const mdProvider = new MarkdownResourceProvider(matcher);
const foam = await bootstrap(matcher, new FileDataStore(), [mdProvider]);
const dataStore = new FileDataStore(readFileFromFs);
const mdProvider = new MarkdownResourceProvider(matcher, dataStore);
const foam = await bootstrap(matcher, dataStore, [mdProvider]);
_workspace = foam.workspace;
});

View File

@@ -7,6 +7,8 @@ import { Range } from '../model/range';
import { FoamWorkspace } from '../model/workspace';
import { FileDataStore, Matcher } from '../services/datastore';
import { Logger } from '../utils/log';
import fs from 'fs';
import { URI } from '../model/uri';
Logger.setLevel('error');
@@ -21,8 +23,12 @@ describe('generateLinkReferences', () => {
beforeAll(async () => {
const matcher = new Matcher([TEST_DATA_DIR.joinPath('__scaffold__')]);
const mdProvider = new MarkdownResourceProvider(matcher);
const foam = await bootstrap(matcher, new FileDataStore(), [mdProvider]);
/** Use fs for reading files in units where vscode.workspace is unavailable */
const readFile = async (uri: URI) =>
(await fs.promises.readFile(uri.toFsPath())).toString();
const dataStore = new FileDataStore(readFile);
const mdProvider = new MarkdownResourceProvider(matcher, dataStore);
const foam = await bootstrap(matcher, dataStore, [mdProvider]);
_workspace = foam.workspace;
});

View File

@@ -1,4 +1,4 @@
import { TEST_DATA_DIR } from '../../test/test-utils';
import { readFileFromFs, TEST_DATA_DIR } from '../../test/test-utils';
import { URI } from '../model/uri';
import { Logger } from '../utils/log';
import { FileDataStore, Matcher, toMatcherPathFormat } from './datastore';
@@ -87,7 +87,7 @@ describe('Matcher', () => {
describe('Datastore', () => {
it('uses the matcher to get the file list', async () => {
const matcher = new Matcher([testFolder], ['**/*.md'], []);
const ds = new FileDataStore();
const ds = new FileDataStore(readFileFromFs);
expect((await ds.list(matcher.include[0])).length).toEqual(4);
});
});

View File

@@ -1,5 +1,4 @@
import micromatch from 'micromatch';
import fs from 'fs';
import { URI } from '../model/uri';
import { Logger } from '../utils/log';
import { glob } from 'glob';
@@ -115,6 +114,8 @@ export interface IDataStore {
* File system based data store
*/
export class FileDataStore implements IDataStore {
constructor(private readFile: (uri: URI) => Promise<string>) {}
async list(glob: string, ignoreGlob?: string | string[]): Promise<URI[]> {
const res = await findAllFiles(glob, {
ignore: ignoreGlob,
@@ -125,7 +126,7 @@ export class FileDataStore implements IDataStore {
async read(uri: URI) {
try {
return (await fs.promises.readFile(uri.toFsPath())).toString();
return await this.readFile(uri);
} catch (e) {
Logger.error(
`FileDataStore: error while reading uri: ${uri.path} - ${e}`

View File

@@ -8,7 +8,7 @@ import { isNone, isSome } from '../utils';
import { Logger } from '../utils/log';
import { URI } from '../model/uri';
import { FoamWorkspace } from '../model/workspace';
import { IDataStore, FileDataStore, IMatcher } from '../services/datastore';
import { IDataStore, IMatcher } from '../services/datastore';
import { IDisposable } from '../common/lifecycle';
import { ResourceProvider } from '../model/provider';
import { createMarkdownParser } from './markdown-parser';
@@ -19,13 +19,13 @@ export class MarkdownResourceProvider implements ResourceProvider {
constructor(
private readonly matcher: IMatcher,
private readonly dataStore: IDataStore,
private readonly watcherInit?: (triggers: {
onDidChange: (uri: URI) => void;
onDidCreate: (uri: URI) => void;
onDidDelete: (uri: URI) => void;
}) => IDisposable[],
private readonly parser: ResourceParser = createMarkdownParser([]),
private readonly dataStore: IDataStore = new FileDataStore()
private readonly parser: ResourceParser = createMarkdownParser([])
) {}
async init(workspace: FoamWorkspace) {

View File

@@ -1,6 +1,5 @@
import { CharCode } from '../common/charCode';
import { posix } from 'path';
import { promises, constants } from 'fs';
/**
* Converts filesystem path to POSIX path. Supported inputs are:
@@ -147,21 +146,6 @@ export function relativeTo(path: string, basePath: string): string {
return posix.relative(basePath, path);
}
/**
* Asynchronously checks if there is an accessible file for a path.
*
* @param fsPath A filesystem-specific path.
* @returns true if an accesible file exists, false otherwise.
*/
export async function existsInFs(fsPath: string) {
try {
await promises.access(fsPath, constants.F_OK);
return true;
} catch (e) {
return false;
}
}
function hasDrive(path: string, idx = 0): boolean {
if (path.length <= idx) {
return false;

View File

@@ -1,13 +1,14 @@
import { workspace, ExtensionContext, window } from 'vscode';
import { MarkdownResourceProvider } from './core/services/markdown-provider';
import { bootstrap } from './core/model/foam';
import { URI } from './core/model/uri';
import { FileDataStore, Matcher } from './core/services/datastore';
import { Logger } from './core/utils/log';
import { features } from './features';
import { VsCodeOutputLogger, exposeLogger } from './services/logging';
import { getIgnoredFilesSetting } from './settings';
import { fromVsCodeUri } from './utils/vsc-utils';
import { fromVsCodeUri, toVsCodeUri } from './utils/vsc-utils';
export async function activate(context: ExtensionContext) {
const logger = new VsCodeOutputLogger();
@@ -18,21 +19,27 @@ export async function activate(context: ExtensionContext) {
Logger.info('Starting Foam');
// Prepare Foam
const dataStore = new FileDataStore();
const readFile = async (uri: URI) =>
(await workspace.fs.readFile(toVsCodeUri(uri))).toString();
const dataStore = new FileDataStore(readFile);
const matcher = new Matcher(
workspace.workspaceFolders.map(dir => fromVsCodeUri(dir.uri)),
['**/*'],
getIgnoredFilesSetting().map(g => g.toString())
);
const markdownProvider = new MarkdownResourceProvider(matcher, triggers => {
const watcher = workspace.createFileSystemWatcher('**/*');
return [
watcher.onDidChange(uri => triggers.onDidChange(fromVsCodeUri(uri))),
watcher.onDidCreate(uri => triggers.onDidCreate(fromVsCodeUri(uri))),
watcher.onDidDelete(uri => triggers.onDidDelete(fromVsCodeUri(uri))),
watcher,
];
});
const markdownProvider = new MarkdownResourceProvider(
matcher,
dataStore,
triggers => {
const watcher = workspace.createFileSystemWatcher('**/*');
return [
watcher.onDidChange(uri => triggers.onDidChange(fromVsCodeUri(uri))),
watcher.onDidCreate(uri => triggers.onDidCreate(fromVsCodeUri(uri))),
watcher.onDidDelete(uri => triggers.onDidDelete(fromVsCodeUri(uri))),
watcher,
];
}
);
const foamPromise = bootstrap(matcher, dataStore, [markdownProvider]);

View File

@@ -3,7 +3,7 @@ import { createMarkdownParser } from '../core/services/markdown-parser';
import { MarkdownResourceProvider } from '../core/services/markdown-provider';
import { FoamGraph } from '../core/model/graph';
import { FoamWorkspace } from '../core/model/workspace';
import { Matcher } from '../core/services/datastore';
import { FileDataStore, Matcher } from '../core/services/datastore';
import {
cleanWorkspace,
closeEditors,
@@ -12,6 +12,7 @@ import {
} from '../test/test-utils-vscode';
import { fromVsCodeUri, toVsCodeUri } from '../utils/vsc-utils';
import { HoverProvider } from './hover-provider';
import { readFileFromFs } from '../test/test-utils';
// We can't use createTestWorkspace from /packages/foam-vscode/src/test/test-utils.ts
// because we need a MarkdownResourceProvider with a real instance of FileDataStore.
@@ -19,7 +20,8 @@ const createWorkspace = () => {
const matcher = new Matcher(
vscode.workspace.workspaceFolders.map(f => fromVsCodeUri(f.uri))
);
const resourceProvider = new MarkdownResourceProvider(matcher);
const dataStore = new FileDataStore(readFileFromFs);
const resourceProvider = new MarkdownResourceProvider(matcher, dataStore);
const workspace = new FoamWorkspace();
workspace.registerProvider(resourceProvider);
return workspace;

View File

@@ -5,14 +5,17 @@ import {
commands,
ProgressLocation,
} from 'vscode';
import * as fs from 'fs';
import { FoamFeature } from '../types';
import {
getWikilinkDefinitionSetting,
LinkReferenceDefinitionsSetting,
} from '../settings';
import { toVsCodePosition, toVsCodeRange } from '../utils/vsc-utils';
import {
toVsCodePosition,
toVsCodeRange,
toVsCodeUri,
} from '../utils/vsc-utils';
import { Foam } from '../core/model/foam';
import { Resource } from '../core/model/note';
import { generateHeading, generateLinkReferences } from '../core/janitor';
@@ -125,7 +128,7 @@ async function runJanitor(foam: Foam) {
text = definitions ? applyTextEdit(text, definitions) : text;
text = heading ? applyTextEdit(text, heading) : text;
return fs.promises.writeFile(note.uri.toFsPath(), text);
return workspace.fs.writeFile(toVsCodeUri(note.uri), Buffer.from(text));
});
await Promise.all(fileWritePromises);

View File

@@ -1,4 +1,4 @@
import { createTestNote } from '../test/test-utils';
import { createTestNote, readFileFromFs } from '../test/test-utils';
import { cleanWorkspace, closeEditors } from '../test/test-utils-vscode';
import { TagItem, TagReference, TagsProvider } from './tags-tree-view';
import { bootstrap, Foam } from '../core/model/foam';
@@ -9,8 +9,9 @@ describe('Tags tree panel', () => {
let _foam: Foam;
let provider: TagsProvider;
const dataStore = new FileDataStore(readFileFromFs);
const matcher = new Matcher([]);
const mdProvider = new MarkdownResourceProvider(matcher);
const mdProvider = new MarkdownResourceProvider(matcher, dataStore);
beforeAll(async () => {
await cleanWorkspace();
@@ -22,7 +23,7 @@ describe('Tags tree panel', () => {
});
beforeEach(async () => {
_foam = await bootstrap(matcher, new FileDataStore(), [mdProvider]);
_foam = await bootstrap(matcher, dataStore, [mdProvider]);
provider = new TagsProvider(_foam, _foam.workspace);
await closeEditors();
});

View File

@@ -1,7 +1,6 @@
import { URI } from '../core/model/uri';
import { existsSync } from 'fs';
import { TextEncoder } from 'util';
import { SnippetString, ViewColumn, window, workspace } from 'vscode';
import { FileType, SnippetString, ViewColumn, window, workspace } from 'vscode';
import { focusNote } from '../utils';
import { fromVsCodeUri, toVsCodeUri } from '../utils/vsc-utils';
import { extractFoamTemplateFrontmatterMetadata } from '../utils/template-frontmatter-parser';
@@ -75,7 +74,7 @@ export async function getTemplateInfo(
templateFallbackText = '',
resolver: Resolver
) {
const templateText = existsSync(templateUri.toFsPath())
const templateText = (await fileExists(templateUri))
? await workspace.fs
.readFile(toVsCodeUri(templateUri))
.then(bytes => bytes.toString())
@@ -137,7 +136,7 @@ export const NoteFactory = {
filepathFallbackURI,
resolver
);
while (existsSync(newFilePath.toFsPath())) {
while (await fileExists(newFilePath)) {
const proposedNewFilepath = await onFileExists(newFilePath);
if (proposedNewFilepath === undefined) {
@@ -224,10 +223,10 @@ export const createTemplate = async (): Promise<void> => {
prompt: `Enter the filename for the new template`,
value: fsPath,
valueSelection: [fsPath.length - defaultFilename.length, fsPath.length - 3],
validateInput: value =>
validateInput: async value =>
value.trim().length === 0
? 'Please enter a value'
: existsSync(value)
: (await fileExists(URI.parse(value)))
? 'File already exists'
: undefined,
});
@@ -252,10 +251,10 @@ async function askUserForFilepathConfirmation(
prompt: `Enter the filename for the new note`,
value: fsPath,
valueSelection: [fsPath.length - defaultFilename.length, fsPath.length - 3],
validateInput: value =>
validateInput: async value =>
value.trim().length === 0
? 'Please enter a value'
: existsSync(value)
: (await fileExists(URI.parse(value)))
? 'File already exists'
: undefined,
});
@@ -286,3 +285,12 @@ export async function determineNewNoteFilepath(
);
return defaultFilepath;
}
async function fileExists(uri: URI): Promise<boolean> {
try {
const stat = await workspace.fs.stat(toVsCodeUri(uri));
return stat.type === FileType.File;
} catch (e) {
return false;
}
}

View File

@@ -1,6 +1,7 @@
/*
* This file should not depend on VS Code as it's used for unit tests
*/
import fs from 'fs';
import { Logger } from '../core/utils/log';
import { Range } from '../core/model/range';
import { URI } from '../core/model/uri';
@@ -35,7 +36,7 @@ export const strToUri = URI.file;
export const createTestWorkspace = () => {
const workspace = new FoamWorkspace();
const matcher = new Matcher([URI.file('/')], ['**/*']);
const provider = new MarkdownResourceProvider(matcher, undefined, undefined, {
const provider = new MarkdownResourceProvider(matcher, {
read: _ => Promise.resolve(''),
list: _ => Promise.resolve([]),
});
@@ -111,3 +112,7 @@ export const randomString = (len = 5) =>
export const getRandomURI = () =>
URI.file('/random-uri-root/' + randomString() + '.md');
/** Use fs for reading files in units where vscode.workspace is unavailable */
export const readFileFromFs = async (uri: URI) =>
(await fs.promises.readFile(uri.toFsPath())).toString();