From a6f5e5c9b52ba83cbcebb29e590452e1b1b5fb11 Mon Sep 17 00:00:00 2001 From: Jason Dent Date: Tue, 12 Dec 2023 12:33:58 +0100 Subject: [PATCH] fix: Add endpoints to VirtualFS Add: - writeFile - getCapabilities --- packages/cspell-io/src/VirtualFS.ts | 228 ++++++++++++++++-- packages/cspell-io/src/VirtualFs.test.ts | 82 +++++-- .../src/__snapshots__/index.test.ts.snap | 1 + .../src/common/CFileResource.test.ts | 113 +++++++++ .../cspell-io/src/common/CFileResource.ts | 12 +- packages/cspell-io/src/common/index.ts | 2 +- packages/cspell-io/src/index.ts | 1 + 7 files changed, 390 insertions(+), 49 deletions(-) create mode 100644 packages/cspell-io/src/common/CFileResource.test.ts diff --git a/packages/cspell-io/src/VirtualFS.ts b/packages/cspell-io/src/VirtualFS.ts index e3036ca52ee..7d0f75fbd0c 100644 --- a/packages/cspell-io/src/VirtualFS.ts +++ b/packages/cspell-io/src/VirtualFS.ts @@ -4,7 +4,7 @@ import type { DirEntry, Disposable, FileReference, FileResource, Stats } from '. type UrlOrReference = URL | FileReference; -type NextProvider = (url: URL) => FileSystem | undefined; +type NextProvider = (url: URL) => ProviderFileSystem | undefined; export interface VirtualFS extends Disposable { registerFileSystemProvider(provider: FileSystemProvider): Disposable; @@ -12,7 +12,7 @@ export interface VirtualFS extends Disposable { /** * Get the fs for a given url. */ - getFS(url: URL): FileSystem | undefined; + getFS(url: URL): FileSystem; /** * The file system. All requests will first use getFileSystem to get the file system before making the request. @@ -25,29 +25,73 @@ export interface VirtualFS extends Disposable { reset(): void; } -export interface FileSystem extends Disposable { +export enum FSCapabilityFlags { + None = 0, + Stat = 1 << 0, + Read = 1 << 1, + Write = 1 << 2, + ReadWrite = Read | Write, + ReadDir = 1 << 3, + WriteDir = 1 << 4, + ReadWriteDir = ReadDir | WriteDir, +} + +interface FileSystemProviderInfo { + name: string; +} + +interface FileSystemBase { stat(url: UrlOrReference): Stats | Promise; readFile(url: UrlOrReference): Promise; - readDirectory?(url: URL): Promise; + readDirectory(url: URL): Promise; + writeFile(file: FileResource): Promise; + /** + * Information about the provider. + * It is up to the provider to define what information is available. + */ + providerInfo: FileSystemProviderInfo; +} + +export interface FileSystem extends FileSystemBase { + getCapabilities(url: URL): FSCapabilities; + hasProvider: boolean; +} + +export interface ProviderFileSystem extends FileSystemBase, Disposable { + /** + * These are the general capabilities for the provider's file system. + * It is possible for a provider to support more capabilities for a given url by providing a getCapabilities function. + */ + capabilities: FSCapabilityFlags; + + /** + * Get the capabilities for a URL. Make it possible for a provider to support more capabilities for a given url. + * These capabilities should be more restrictive than the general capabilities. + * @param url - the url to try + * @returns the capabilities for the url. + */ + getCapabilities?: (url: URL) => FSCapabilities; } export interface FileSystemProvider extends Partial { + /** Name of the Provider */ + name: string; /** * Get the file system for a given url. The provider is cached based upon the protocol and hostname. * @param url - the url to get the file system for. * @param next - call this function to get the next provider to try. This is useful for chaining providers that operate on the same protocol. */ - getFileSystem(url: URL, next: NextProvider): FileSystem | undefined; + getFileSystem(url: URL, next: NextProvider): ProviderFileSystem | undefined; } class CVirtualFS implements VirtualFS { private readonly providers = new Set(); - private cachedFs = new Map(); + private cachedFs = new Map(); private revCacheFs = new Map>(); readonly fs: Required; constructor() { - this.fs = fsPassThrough((url) => this.getFS(url)); + this.fs = fsPassThrough((url) => this._getFS(url)); } registerFileSystemProvider(provider: FileSystemProvider): Disposable { @@ -63,11 +107,16 @@ class CVirtualFS implements VirtualFS { }; } - getFS(url: URL): FileSystem | undefined { + getFS(url: URL): FileSystem { + return this._getFS(url); + } + + private _getFS(url: URL): WrappedProviderFs { const key = `${url.protocol}${url.hostname}`; - if (this.cachedFs.has(key)) { - return this.cachedFs.get(key); + const cached = this.cachedFs.get(key); + if (cached) { + return cached; } const fnNext = (provider: FileSystemProvider, next: NextProvider) => { @@ -96,7 +145,7 @@ class CVirtualFS implements VirtualFS { next = fnNext(provider, next); } - const fs = next(url); + const fs = new WrappedProviderFs(next(url)); this.cachedFs.set(key, fs); return fs; } @@ -110,7 +159,7 @@ class CVirtualFS implements VirtualFS { private disposeOfCachedFs(): void { for (const [key, fs] of [...this.cachedFs].reverse()) { try { - fs?.dispose?.(); + WrappedProviderFs.disposeOf(fs); } catch (e) { // continue - we are cleaning up. } @@ -132,12 +181,12 @@ class CVirtualFS implements VirtualFS { } } -function fsPassThrough(fs: (url: URL) => FileSystem | undefined): Required { +function fsPassThrough(fs: (url: URL) => WrappedProviderFs): Required { function gfs(ur: UrlOrReference, name: string): FileSystem { const url = urlOrReferenceToUrl(ur); const f = fs(url); - if (!f) - throw new VFSErrorUnhandledRequest( + if (!f.hasProvider) + throw new VFSErrorUnsupportedRequest( name, url, ur instanceof URL ? undefined : { url: ur.url.toString(), encoding: ur.encoding }, @@ -145,13 +194,13 @@ function fsPassThrough(fs: (url: URL) => FileSystem | undefined): Required gfs(url, 'stat').stat(url), readFile: async (url) => gfs(url, 'readFile').readFile(url), - readDirectory: async (url) => { - const fs = gfs(url, 'readDirectory'); - return fs.readDirectory ? fs.readDirectory(url) : Promise.resolve([]); - }, - dispose: () => undefined, + writeFile: async (file) => gfs(file, 'writeFile').writeFile(file), + readDirectory: async (url) => gfs(url, 'readDirectory').readDirectory(url), + getCapabilities: (url) => gfs(url, 'getCapabilities').getCapabilities(url), }; } @@ -167,15 +216,20 @@ export function createVirtualFS(cspellIO?: CSpellIO): VirtualFS { } function cspellIOToFsProvider(cspellIO: CSpellIO): FileSystemProvider { + const name = 'CSpellIO'; const supportedProtocols = new Set(['file:', 'http:', 'https:']); - const fs: FileSystem = { + const fs: ProviderFileSystem = { + providerInfo: { name }, stat: (url) => cspellIO.getStat(url), readFile: (url) => cspellIO.readFile(url), readDirectory: (url) => cspellIO.readDirectory(url), + writeFile: (file) => cspellIO.writeFile(file.url, file.content), dispose: () => undefined, + capabilities: FSCapabilityFlags.Stat | FSCapabilityFlags.ReadWrite | FSCapabilityFlags.ReadDir, }; return { + name, getFileSystem: (url, _next) => { return supportedProtocols.has(url.protocol) ? fs : undefined; }, @@ -191,13 +245,19 @@ export function getDefaultVirtualFs(): VirtualFS { return defaultVirtualFs; } +function wrapError(e: unknown): unknown { + if (e instanceof VFSError) return e; + // return new VFSError(e instanceof Error ? e.message : String(e), { cause: e }); + return e; +} + export class VFSError extends Error { - constructor(message: string, options?: { cause?: Error }) { + constructor(message: string, options?: { cause?: unknown }) { super(message, options); } } -export class VFSErrorUnhandledRequest extends VFSError { +export class VFSErrorUnsupportedRequest extends VFSError { public readonly url?: string | undefined; constructor( @@ -205,7 +265,127 @@ export class VFSErrorUnhandledRequest extends VFSError { url?: URL | string, public readonly parameters?: unknown, ) { - super(`Unhandled request: ${request}`); + super(`Unsupported request: ${request}`); this.url = url?.toString(); } } + +export interface FSCapabilities { + readonly flags: FSCapabilityFlags; + readonly readFile: boolean; + readonly writeFile: boolean; + readonly readDirectory: boolean; + readonly writeDirectory: boolean; + readonly stat: boolean; +} + +class CFsCapabilities { + constructor(readonly flags: FSCapabilityFlags) {} + + get readFile(): boolean { + return !!(this.flags & FSCapabilityFlags.Read); + } + + get writeFile(): boolean { + return !!(this.flags & FSCapabilityFlags.Write); + } + + get readDirectory(): boolean { + return !!(this.flags & FSCapabilityFlags.ReadDir); + } + + get writeDirectory(): boolean { + return !!(this.flags & FSCapabilityFlags.WriteDir); + } + + get stat(): boolean { + return !!(this.flags & FSCapabilityFlags.Stat); + } +} + +export function fsCapabilities(flags: FSCapabilityFlags): FSCapabilities { + return new CFsCapabilities(flags); +} + +class WrappedProviderFs implements FileSystem { + readonly hasProvider: boolean; + readonly capabilities: FSCapabilityFlags; + readonly providerInfo: FileSystemProviderInfo; + private _capabilities: FSCapabilities; + constructor(private readonly fs: ProviderFileSystem | undefined) { + this.hasProvider = !!fs; + this.capabilities = fs?.capabilities || FSCapabilityFlags.None; + this._capabilities = fsCapabilities(this.capabilities); + this.providerInfo = fs?.providerInfo || { name: 'unknown' }; + } + + getCapabilities(url: URL): FSCapabilities { + if (this.fs?.getCapabilities) return this.fs.getCapabilities(url); + + return this._capabilities; + } + + async stat(url: UrlOrReference): Promise { + try { + checkCapabilityOrThrow( + this.fs, + this.capabilities, + FSCapabilityFlags.Stat, + 'stat', + urlOrReferenceToUrl(url), + ); + return await this.fs.stat(url); + } catch (e) { + throw wrapError(e); + } + } + + async readFile(url: UrlOrReference): Promise { + try { + checkCapabilityOrThrow( + this.fs, + this.capabilities, + FSCapabilityFlags.Read, + 'readFile', + urlOrReferenceToUrl(url), + ); + return await this.fs.readFile(url); + } catch (e) { + throw wrapError(e); + } + } + + async readDirectory(url: URL): Promise { + try { + checkCapabilityOrThrow(this.fs, this.capabilities, FSCapabilityFlags.ReadDir, 'readDirectory', url); + return await this.fs.readDirectory(url); + } catch (e) { + throw wrapError(e); + } + } + + async writeFile(file: FileResource): Promise { + try { + checkCapabilityOrThrow(this.fs, this.capabilities, FSCapabilityFlags.Write, 'writeFile', file.url); + return await this.fs.writeFile(file); + } catch (e) { + throw wrapError(e); + } + } + + static disposeOf(fs: FileSystem): void { + fs instanceof WrappedProviderFs && fs.fs?.dispose(); + } +} + +function checkCapabilityOrThrow( + fs: ProviderFileSystem | undefined, + capabilities: FSCapabilityFlags, + flag: FSCapabilityFlags, + name: string, + url: URL, +): asserts fs is ProviderFileSystem { + if (!(capabilities & flag)) { + throw new VFSErrorUnsupportedRequest(name, url); + } +} diff --git a/packages/cspell-io/src/VirtualFs.test.ts b/packages/cspell-io/src/VirtualFs.test.ts index 8567b98ab92..d45e8da9377 100644 --- a/packages/cspell-io/src/VirtualFs.test.ts +++ b/packages/cspell-io/src/VirtualFs.test.ts @@ -5,8 +5,8 @@ import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; import { CFileResource } from './common/index.js'; import { toFileURL } from './node/file/url.js'; import { pathToSample as ps } from './test/test.helper.js'; -import type { FileSystem, FileSystemProvider, VirtualFS } from './VirtualFS.js'; -import { createVirtualFS, getDefaultVirtualFs, VFSErrorUnhandledRequest } from './VirtualFS.js'; +import type { FileSystemProvider, ProviderFileSystem, VirtualFS } from './VirtualFS.js'; +import { createVirtualFS, FSCapabilityFlags, getDefaultVirtualFs, VFSErrorUnsupportedRequest } from './VirtualFS.js'; const sc = expect.stringContaining; const oc = expect.objectContaining; @@ -30,15 +30,15 @@ describe('VirtualFs', () => { mockGetFileSystem.mockImplementation((url) => (url.protocol === 'file:' ? mfs : undefined)); const d = virtualFs.registerFileSystemProvider(provider); const fs = virtualFs.getFS(new URL('file:///')); - expect(fs).toBeDefined(); - expect(fs).toBe(mfs); + expect(fs.hasProvider).toBe(true); + expect(fs.providerInfo.name).toBe('mockFileSystemProvider'); expect(mockGetFileSystem).toHaveBeenCalledTimes(1); // ask again const fs2 = virtualFs.getFS(new URL('file:///')); - expect(fs2).toBeDefined(); - expect(fs2).toBe(mfs); + expect(fs2.hasProvider).toBe(true); + expect(fs2.providerInfo).toBe(mfs.providerInfo); expect(mockGetFileSystem).toHaveBeenCalledTimes(1); @@ -47,8 +47,8 @@ describe('VirtualFs', () => { // ask again const fs3 = virtualFs.getFS(new URL('file:///')); expect(fs3).toBeDefined(); - expect(fs3).not.toBe(mfs); - expect(fs3).toBe(defaultFs); + expect(fs3.hasProvider).toBe(true); + expect(fs3.providerInfo).toBe(defaultFs.providerInfo); }); test('should dispose of everything', () => { @@ -59,8 +59,8 @@ describe('VirtualFs', () => { mockGetFileSystem.mockImplementation((url) => (url.protocol === 'file:' ? mfs : undefined)); virtualFs.registerFileSystemProvider(provider); const fs = virtualFs.getFS(new URL('file:///')); - expect(fs).toBeDefined(); - expect(fs).toBe(mfs); + expect(fs.hasProvider).toBe(true); + expect(fs.providerInfo.name).toBe('mockFileSystemProvider'); expect(mockGetFileSystem).toHaveBeenCalledTimes(1); @@ -75,17 +75,20 @@ describe('VirtualFs', () => { vi.mocked(provider.getFileSystem).mockImplementation((url) => (url.protocol === 'untitled:' ? mfs : undefined)); const d = virtualFs.registerFileSystemProvider(provider); const fs = virtualFs.getFS(new URL('file:///')); - expect(fs).toBeDefined(); + expect(fs.hasProvider).toBe(true); expect(fs).not.toBe(mfs); d.dispose(); }); - test('should not find a FS', () => { + test('should not find a FS', async () => { const provider = mockFileSystemProvider(); vi.mocked(provider.getFileSystem).mockImplementation((url, next) => next(url)); virtualFs.registerFileSystemProvider(provider); - const fs = virtualFs.getFS(new URL('ftp://example.com/data.json')); - expect(fs).toBeUndefined(); + const url = new URL('ftp://example.com/data.json'); + const fs = virtualFs.getFS(url); + expect(fs.hasProvider).toBe(false); + + await expect(fs.readFile(url)).rejects.toThrowError('Unsupported request: readFile'); }); test('should have a file: default when calling next', () => { @@ -94,7 +97,7 @@ describe('VirtualFs', () => { vi.mocked(provider.getFileSystem).mockImplementation((url, next) => next(url)); virtualFs.registerFileSystemProvider(provider); const fs = virtualFs.getFS(new URL('file:///')); - expect(fs).toBeDefined(); + expect(fs.hasProvider).toBe(true); expect(fs).not.toBe(mfs); }); @@ -129,22 +132,22 @@ describe('VirtualFs', () => { test('try unsupported readFile', async () => { const fs = virtualFs.fs; const result = fs.readFile(new URL('ftp://example.com/data.json')); - await expect(result).rejects.toEqual(Error('Unhandled request: readFile')); - await expect(result).rejects.toBeInstanceOf(VFSErrorUnhandledRequest); + await expect(result).rejects.toEqual(Error('Unsupported request: readFile')); + await expect(result).rejects.toBeInstanceOf(VFSErrorUnsupportedRequest); }); test('try unsupported stat', async () => { const fs = virtualFs.fs; const result = fs.stat(new URL('ftp://example.com/data.json')); - await expect(result).rejects.toEqual(Error('Unhandled request: stat')); - await expect(result).rejects.toBeInstanceOf(VFSErrorUnhandledRequest); + await expect(result).rejects.toEqual(Error('Unsupported request: stat')); + await expect(result).rejects.toBeInstanceOf(VFSErrorUnsupportedRequest); }); test('try unsupported readDirectory', async () => { const fs = virtualFs.fs; const result = fs.readDirectory(new URL('ftp://example.com/data.json')); - await expect(result).rejects.toEqual(Error('Unhandled request: readDirectory')); - await expect(result).rejects.toBeInstanceOf(VFSErrorUnhandledRequest); + await expect(result).rejects.toEqual(Error('Unsupported request: readDirectory')); + await expect(result).rejects.toBeInstanceOf(VFSErrorUnsupportedRequest); }); test.each` @@ -195,20 +198,53 @@ describe('VirtualFs', () => { const r = fs.stat(url); await expect(r).rejects.toEqual(expected); }); + + test('writeFile', async () => { + const provider = mockFileSystemProvider(); + const mfs = mockFileSystem(); + vi.mocked(provider.getFileSystem).mockImplementation((_url) => mfs); + const d = virtualFs.registerFileSystemProvider(provider); + const mockedWriteFile = vi.mocked(mfs.writeFile); + mockedWriteFile.mockImplementation((file) => Promise.resolve({ url: file.url })); + const fs = virtualFs.fs; + const file = { url: new URL('file:///hello.txt'), content: 'Hello World' }; + const result = await fs.writeFile(file); + expect(mockedWriteFile).toHaveBeenCalledTimes(1); + expect(mockedWriteFile).toHaveBeenLastCalledWith(file); + expect(result).not.toBe(file); + expect(result).toStrictEqual(oc({ url: file.url })); + d.dispose(); + }); + + test('fsCapabilities', () => { + const fs = virtualFs.fs; + const capabilities = fs.getCapabilities(new URL('file:///hello.txt')); + expect(capabilities).toBeDefined(); + expect(capabilities.readFile).toBe(true); + expect(capabilities.writeFile).toBe(true); + expect(capabilities.stat).toBe(true); + expect(capabilities.readDirectory).toBe(true); + expect(capabilities.writeDirectory).toBe(false); + }); }); -function mockFileSystem(): FileSystem { - const p: FileSystem = { +function mockFileSystem(): ProviderFileSystem { + const p: ProviderFileSystem = { + providerInfo: { name: 'mockFileSystemProvider' }, + capabilities: FSCapabilityFlags.Stat | FSCapabilityFlags.ReadWrite | FSCapabilityFlags.ReadDir, stat: vi.fn(), readFile: vi.fn(), readDirectory: vi.fn(), + writeFile: vi.fn(), dispose: vi.fn(), + getCapabilities: vi.fn(), }; return p; } function mockFileSystemProvider(): FileSystemProvider { const p: FileSystemProvider = { + name: 'mockFileSystemProvider', getFileSystem: vi.fn(), dispose: vi.fn(), }; diff --git a/packages/cspell-io/src/__snapshots__/index.test.ts.snap b/packages/cspell-io/src/__snapshots__/index.test.ts.snap index e8c91630362..2b571ff0a77 100644 --- a/packages/cspell-io/src/__snapshots__/index.test.ts.snap +++ b/packages/cspell-io/src/__snapshots__/index.test.ts.snap @@ -6,6 +6,7 @@ exports[`index > api 1`] = ` "CFileResource => function", "CSpellIONode => function", "asyncIterableToArray => function", + "createTextFileResource => function", "createVirtualFS => function", "encodeDataUrl => function", "getDefaultCSpellIO => function", diff --git a/packages/cspell-io/src/common/CFileResource.test.ts b/packages/cspell-io/src/common/CFileResource.test.ts new file mode 100644 index 00000000000..886cd0925db --- /dev/null +++ b/packages/cspell-io/src/common/CFileResource.test.ts @@ -0,0 +1,113 @@ +import { describe, expect, test } from 'vitest'; + +import { CFileResource, fromFileResource } from './CFileResource.js'; + +describe('CFileResource', () => { + describe('from', () => { + test('should create a CFileResource from a FileResource', () => { + // Arrange + const fileResource = { + url: new URL('https://example.com/file.txt'), + content: 'Hello, world!', + encoding: 'utf-8', + } as const; + + // Act + const cFileResource = CFileResource.from(fileResource); + + // Assert + expect(cFileResource.url).toEqual(fileResource.url); + expect(cFileResource.content).toEqual(fileResource.content); + expect(cFileResource.encoding).toEqual(fileResource.encoding); + expect(cFileResource.baseFilename).toBe('file.txt'); + expect(cFileResource.gz).toBeFalsy(); + }); + + test('should create a CFileResource from a FileResource and content', () => { + // Arrange + const fileResource = { + url: new URL('https://example.com/file.txt'), + content: 'Hello, world!', + encoding: 'utf-8', + } as const; + + const content = 'Welcome to a new day!'; + + // Act + const cFileResource1 = CFileResource.from(fileResource); + const cFileResource2 = CFileResource.from(cFileResource1, content); + const cFileResource3 = CFileResource.from(cFileResource1); + + // Assert + expect(cFileResource1).not.toBe(cFileResource2); + expect(cFileResource1).toBe(cFileResource3); + expect(cFileResource2.url).toEqual(fileResource.url); + expect(cFileResource2.content).toEqual(content); + expect(cFileResource2.encoding).toEqual(fileResource.encoding); + expect(cFileResource2.baseFilename).toBe('file.txt'); + expect(cFileResource2.gz).toBeFalsy(); + }); + + test('should create a CFileResource from a FileReference and content', () => { + // Arrange + const fileReference = { + url: new URL('https://example.com/file.txt'), + }; + const content = 'Hello, world!'; + + // Act + const cFileResource = CFileResource.from(fileReference, content); + + // Assert + expect(cFileResource.url).toBe(fileReference.url); + expect(cFileResource.content).toEqual(content); + expect(cFileResource.encoding).toBeUndefined(); + expect(cFileResource.baseFilename).toBe('file.txt'); + expect(cFileResource.gz).toBe(false); + }); + + test('should create a CFileResource from a URL, content, and optional parameters', () => { + // Arrange + const url = new URL('https://example.com/file.txt'); + const content = 'Hello, world!'; + const encoding = 'utf-8'; + const baseFilename = 'file.txt'; + const gz = true; + + // Act + const cFileResource = CFileResource.from(url, content, encoding, baseFilename, gz); + + // Assert + expect(cFileResource.url).toEqual(url); + expect(cFileResource.content).toEqual(content); + expect(cFileResource.encoding).toEqual(encoding); + expect(cFileResource.baseFilename).toBe('file.txt'); + expect(cFileResource.gz).toEqual(gz); + }); + }); + + describe('fromFileResource', () => { + test('should create a CFileResource from a FileResource', () => { + // Arrange + + const content = 'Hello, world!'; + + const fileResource = { + url: new URL('https://example.com/file.txt'), + content: Buffer.from(content), + encoding: 'utf-8', + } as const; + + // Act + const cFileResource = fromFileResource(fileResource); + + // Assert + expect(cFileResource.url).toEqual(fileResource.url); + expect(cFileResource.content).toEqual(fileResource.content); + expect(cFileResource.getText()).toEqual(content); + expect(cFileResource.encoding).toEqual(fileResource.encoding); + expect(cFileResource.baseFilename).toBe('file.txt'); + expect(cFileResource.gz).toBeFalsy(); + }); + }); +}); diff --git a/packages/cspell-io/src/common/CFileResource.ts b/packages/cspell-io/src/common/CFileResource.ts index 2a31d9773f6..74a7219ad07 100644 --- a/packages/cspell-io/src/common/CFileResource.ts +++ b/packages/cspell-io/src/common/CFileResource.ts @@ -64,7 +64,13 @@ export class CFileResource implements TextFileResource { baseFilename?: string | undefined, gz?: boolean, ): CFileResource { - if (CFileResource.isCFileResource(urlOrFileResource)) return urlOrFileResource; + if (CFileResource.isCFileResource(urlOrFileResource)) { + if (content) { + const { url, encoding, baseFilename, gz } = urlOrFileResource; + return new CFileResource(url, content, encoding, baseFilename, gz); + } + return urlOrFileResource; + } if (urlOrFileResource instanceof URL) { assert(content !== undefined); return new CFileResource(urlOrFileResource, content, encoding, baseFilename, gz); @@ -84,3 +90,7 @@ export class CFileResource implements TextFileResource { ); } } + +export function fromFileResource(fileResource: FileResource): TextFileResource { + return CFileResource.from(fileResource); +} diff --git a/packages/cspell-io/src/common/index.ts b/packages/cspell-io/src/common/index.ts index 0316493d51b..1befafff20e 100644 --- a/packages/cspell-io/src/common/index.ts +++ b/packages/cspell-io/src/common/index.ts @@ -1,2 +1,2 @@ export { CFileReference } from './CFileReference.js'; -export { CFileResource } from './CFileResource.js'; +export { CFileResource, fromFileResource as createTextFileResource } from './CFileResource.js'; diff --git a/packages/cspell-io/src/index.ts b/packages/cspell-io/src/index.ts index 656e3bd8ed7..ab8171a2fb8 100644 --- a/packages/cspell-io/src/index.ts +++ b/packages/cspell-io/src/index.ts @@ -1,5 +1,6 @@ export { toArray as asyncIterableToArray } from './async/asyncIterable.js'; export * from './common/index.js'; +export { createTextFileResource } from './common/index.js'; export type { CSpellIO } from './CSpellIO.js'; export { CSpellIONode, getDefaultCSpellIO } from './CSpellIONode.js'; export {