+10
@@ -0,0 +1,10 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2021 TypeFox GmbH
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the MIT License, which is available in the project root.
|
||||
*
|
||||
* @module langium/test
|
||||
*/
|
||||
export * from './langium-test.js';
|
||||
export * from './virtual-file-system.js';
|
||||
//# sourceMappingURL=index.d.ts.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/test/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,cAAc,mBAAmB,CAAC;AAClC,cAAc,0BAA0B,CAAC"}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2021 TypeFox GmbH
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the MIT License, which is available in the project root.
|
||||
*
|
||||
* @module langium/test
|
||||
*/
|
||||
export * from './langium-test.js';
|
||||
export * from './virtual-file-system.js';
|
||||
//# sourceMappingURL=index.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/test/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,cAAc,mBAAmB,CAAC;AAClC,cAAc,0BAA0B,CAAC"}
|
||||
+245
@@ -0,0 +1,245 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2021 TypeFox GmbH
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the MIT License, which is available in the project root.
|
||||
******************************************************************************/
|
||||
import type { CompletionItem, CompletionList, Diagnostic, DocumentSymbol, FoldingRange, FormattingOptions, Range, ReferenceParams, SemanticTokenTypes, TextDocumentIdentifier, TextDocumentPositionParams, WorkspaceSymbol } from 'vscode-languageserver-protocol';
|
||||
import { CodeAction, DiagnosticSeverity } from 'vscode-languageserver-types';
|
||||
import type { LangiumServices, LangiumSharedLSPServices } from '../lsp/lsp-services.js';
|
||||
import { SemanticTokensDecoder } from '../lsp/semantic-token-provider.js';
|
||||
import type { ParserOptions } from '../parser/langium-parser.js';
|
||||
import type { LangiumCoreServices, LangiumSharedCoreServices } from '../services.js';
|
||||
import type { AstNode, Properties } from '../syntax-tree.js';
|
||||
import type { AsyncDisposable } from '../utils/disposable.js';
|
||||
import { Disposable } from '../utils/disposable.js';
|
||||
import type { BuildOptions } from '../workspace/document-builder.js';
|
||||
import { TextDocument, type LangiumDocument } from '../workspace/documents.js';
|
||||
export interface ParseHelperOptions extends BuildOptions {
|
||||
/**
|
||||
* Specifies the URI of the generated document. Will use a counter variable if not specified.
|
||||
*/
|
||||
documentUri?: string;
|
||||
/**
|
||||
* Options passed to the LangiumParser.
|
||||
*/
|
||||
parserOptions?: ParserOptions;
|
||||
}
|
||||
export declare function parseHelper<T extends AstNode = AstNode>(services: LangiumCoreServices): (input: string, options?: ParseHelperOptions) => Promise<LangiumDocument<T>>;
|
||||
export type ExpectFunction = (actual: unknown, expected: unknown, message?: string) => void;
|
||||
export interface ExpectedBase {
|
||||
/**
|
||||
* Document content.
|
||||
* Use `<|>` and `<|...|>` to mark special items that are relevant to the test case.
|
||||
*/
|
||||
text: string;
|
||||
/**
|
||||
* Parse options used to parse the {@link text} property.
|
||||
*/
|
||||
parseOptions?: ParseHelperOptions;
|
||||
/**
|
||||
* String to mark indices for test cases. `<|>` by default.
|
||||
*/
|
||||
indexMarker?: string;
|
||||
/**
|
||||
* String to mark start indices for test cases. `<|` by default.
|
||||
*/
|
||||
rangeStartMarker?: string;
|
||||
/**
|
||||
* String to mark end indices for test cases. `|>` by default.
|
||||
*/
|
||||
rangeEndMarker?: string;
|
||||
/**
|
||||
* Whether to dispose the created documents right after performing the check.
|
||||
*
|
||||
* Defaults to `false`.
|
||||
*/
|
||||
disposeAfterCheck?: boolean;
|
||||
}
|
||||
export interface ExpectedHighlight extends ExpectedBase {
|
||||
index?: number;
|
||||
rangeIndex?: number | number[];
|
||||
}
|
||||
/**
|
||||
* Testing utility function for the `textDocument/documentHighlight` LSP request
|
||||
*
|
||||
* @returns A function that performs the assertion
|
||||
*/
|
||||
export declare function expectHighlight(services: LangiumServices): (input: ExpectedHighlight) => Promise<AsyncDisposable>;
|
||||
export interface ExpectedSymbolsList extends ExpectedBase {
|
||||
expectedSymbols: Array<string | DocumentSymbol>;
|
||||
symbolToString?: (item: DocumentSymbol) => string;
|
||||
}
|
||||
export interface ExpectedSymbolsCallback extends ExpectedBase {
|
||||
assert: (symbols: DocumentSymbol[]) => void;
|
||||
}
|
||||
export type ExpectedSymbols = ExpectedSymbolsList | ExpectedSymbolsCallback;
|
||||
export declare function expectSymbols(services: LangiumServices): (input: ExpectedSymbols) => Promise<AsyncDisposable>;
|
||||
export interface ExpectedWorkspaceSymbolsBase {
|
||||
query?: string;
|
||||
}
|
||||
export interface ExpectedWorkspaceSymbolsList extends ExpectedWorkspaceSymbolsBase {
|
||||
expectedSymbols: Array<string | WorkspaceSymbol>;
|
||||
symbolToString?: (item: WorkspaceSymbol) => string;
|
||||
}
|
||||
export interface ExpectedWorkspaceSymbolsCallback extends ExpectedWorkspaceSymbolsBase {
|
||||
assert: (symbols: WorkspaceSymbol[]) => void;
|
||||
}
|
||||
export type ExpectedWorkspaceSymbols = ExpectedWorkspaceSymbolsList | ExpectedWorkspaceSymbolsCallback;
|
||||
export declare function expectWorkspaceSymbols(services: LangiumSharedLSPServices): (input: ExpectedWorkspaceSymbols) => Promise<void>;
|
||||
export interface ExpectedFoldings extends ExpectedBase {
|
||||
assert?: (foldings: FoldingRange[], expected: Array<[number, number]>) => void;
|
||||
}
|
||||
export declare function expectFoldings(services: LangiumServices): (input: ExpectedFoldings) => Promise<AsyncDisposable>;
|
||||
export declare function textDocumentParams(document: LangiumDocument): {
|
||||
textDocument: TextDocumentIdentifier;
|
||||
};
|
||||
export interface ExpectedCompletionItems extends ExpectedBase {
|
||||
index: number;
|
||||
expectedItems: Array<string | CompletionItem>;
|
||||
itemToString?: (item: CompletionItem) => string;
|
||||
}
|
||||
export interface ExpectedCompletionCallback extends ExpectedBase {
|
||||
index: number;
|
||||
assert: (completions: CompletionList) => void;
|
||||
}
|
||||
export type ExpectedCompletion = ExpectedCompletionItems | ExpectedCompletionCallback;
|
||||
export declare function expectCompletion(services: LangiumServices): (expectedCompletion: ExpectedCompletion) => Promise<AsyncDisposable>;
|
||||
export interface ExpectedGoToDefinition extends ExpectedBase {
|
||||
index: number;
|
||||
rangeIndex: number | number[];
|
||||
}
|
||||
export declare function expectGoToDefinition(services: LangiumServices): (expectedGoToDefinition: ExpectedGoToDefinition) => Promise<AsyncDisposable>;
|
||||
export interface ExpectedFindReferences extends ExpectedBase {
|
||||
includeDeclaration: boolean;
|
||||
}
|
||||
export declare function expectFindReferences(services: LangiumServices): (expectedFindReferences: ExpectedFindReferences) => Promise<AsyncDisposable>;
|
||||
export declare function referenceParams(document: LangiumDocument, offset: number, includeDeclaration: boolean): ReferenceParams;
|
||||
export interface ExpectedHover extends ExpectedBase {
|
||||
index: number;
|
||||
hover?: string | RegExp;
|
||||
}
|
||||
export declare function expectHover(services: LangiumServices): (expectedHover: ExpectedHover) => Promise<AsyncDisposable>;
|
||||
export interface ExpectFormatting {
|
||||
/**
|
||||
* Document content before formatting.
|
||||
*/
|
||||
before: string;
|
||||
/**
|
||||
* Expected document content after formatting.
|
||||
* The test case will compare this to the actual formatted document.
|
||||
*/
|
||||
after: string;
|
||||
/**
|
||||
* Parse options used to parse the {@link text} property.
|
||||
*/
|
||||
parseOptions?: ParseHelperOptions;
|
||||
/**
|
||||
* If given, only the specified range will be affected by the formatter
|
||||
*/
|
||||
range?: Range;
|
||||
/**
|
||||
* Options used by the formatter. Default:
|
||||
* ```ts
|
||||
* {
|
||||
* insertSpaces: true,
|
||||
* tabSize: 4
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
options?: FormattingOptions;
|
||||
/**
|
||||
* Whether to dispose the created documents right after performing the check.
|
||||
*
|
||||
* Defaults to `false`.
|
||||
*/
|
||||
disposeAfterCheck?: boolean;
|
||||
}
|
||||
export declare function expectFormatting(services: LangiumServices): (expectedFormatting: ExpectFormatting) => Promise<AsyncDisposable>;
|
||||
export declare function textDocumentPositionParams(document: LangiumDocument, offset: number): TextDocumentPositionParams;
|
||||
export declare function parseDocument<T extends AstNode = AstNode>(services: LangiumCoreServices, input: string, options?: ParseHelperOptions): Promise<LangiumDocument<T>>;
|
||||
export declare function replaceIndices(base: ExpectedBase): {
|
||||
output: string;
|
||||
indices: number[];
|
||||
ranges: Array<[number, number]>;
|
||||
};
|
||||
export interface ValidationResult<T extends AstNode = AstNode> extends AsyncDisposable {
|
||||
diagnostics: Diagnostic[];
|
||||
document: LangiumDocument<T>;
|
||||
}
|
||||
export type ValidationHelperOptions = ParseHelperOptions & {
|
||||
failOnParsingErrors?: boolean;
|
||||
};
|
||||
export declare function validationHelper<T extends AstNode = AstNode>(services: LangiumCoreServices): (input: string, options?: ValidationHelperOptions) => Promise<ValidationResult<T>>;
|
||||
export type ExpectDiagnosticOptionsWithoutContent<T extends AstNode = AstNode> = ExpectDiagnosticCode & ExpectDiagnosticData & (ExpectDiagnosticAstOptions<T> | ExpectDiagnosticRangeOptions | ExpectDiagnosticOffsetOptions);
|
||||
export type ExpectDiagnosticOptions<T extends AstNode = AstNode> = ExpectDiagnosticContent & ExpectDiagnosticOptionsWithoutContent<T>;
|
||||
export interface ExpectDiagnosticContent {
|
||||
message?: string | RegExp;
|
||||
severity?: DiagnosticSeverity;
|
||||
}
|
||||
export interface ExpectDiagnosticCode {
|
||||
code?: string;
|
||||
}
|
||||
export interface ExpectDiagnosticData {
|
||||
data?: unknown;
|
||||
}
|
||||
export interface ExpectDiagnosticAstOptions<T extends AstNode> {
|
||||
node?: T;
|
||||
property?: Properties<T> | {
|
||||
name: Properties<T>;
|
||||
index?: number;
|
||||
};
|
||||
}
|
||||
export interface ExpectDiagnosticRangeOptions {
|
||||
range: Range;
|
||||
}
|
||||
export interface ExpectDiagnosticOffsetOptions {
|
||||
offset: number;
|
||||
length: number;
|
||||
}
|
||||
export type Predicate<T> = (arg: T) => boolean;
|
||||
export declare function isDiagnosticDataEqual(lhs: unknown, rhs: unknown): boolean;
|
||||
export declare function isRangeEqual(lhs: Range, rhs: Range): boolean;
|
||||
export declare function rangeToString(range: Range): string;
|
||||
export declare function filterByOptions<T extends AstNode = AstNode, N extends AstNode = AstNode>(validationResult: ValidationResult<T>, options: ExpectDiagnosticOptions<N>): Diagnostic[];
|
||||
export declare function expectNoIssues<T extends AstNode = AstNode, N extends AstNode = AstNode>(validationResult: ValidationResult<T>, filterOptions?: ExpectDiagnosticOptions<N>): void;
|
||||
export declare function expectIssue<T extends AstNode = AstNode, N extends AstNode = AstNode>(validationResult: ValidationResult<T>, filterOptions?: ExpectDiagnosticOptions<N>): void;
|
||||
export declare function expectError<T extends AstNode = AstNode, N extends AstNode = AstNode>(validationResult: ValidationResult<T>, message: string | RegExp, filterOptions: ExpectDiagnosticOptionsWithoutContent<N>): void;
|
||||
export declare function expectWarning<T extends AstNode = AstNode, N extends AstNode = AstNode>(validationResult: ValidationResult<T>, message: string | RegExp, filterOptions: ExpectDiagnosticOptionsWithoutContent<N>): void;
|
||||
export declare function printDiagnostics(diagnostics: Diagnostic[] | undefined): string;
|
||||
/**
|
||||
* Add the given document to the `TextDocuments` service, simulating it being opened in an editor.
|
||||
*
|
||||
* @deprecated Since 3.2.0. Use `set`/`delete` from `TextDocuments` instead.
|
||||
*/
|
||||
export declare function setTextDocument(services: LangiumServices | LangiumSharedLSPServices, document: TextDocument): Disposable;
|
||||
export declare function clearDocuments(services: LangiumCoreServices | LangiumSharedCoreServices, documents?: LangiumDocument[]): Promise<void>;
|
||||
export interface DecodedSemanticTokensWithRanges {
|
||||
tokens: SemanticTokensDecoder.DecodedSemanticToken[];
|
||||
ranges: Array<[number, number]>;
|
||||
}
|
||||
export declare function highlightHelper<T extends AstNode = AstNode>(services: LangiumServices): (input: string, options?: ParseHelperOptions) => Promise<DecodedSemanticTokensWithRanges>;
|
||||
export interface DecodedTokenOptions {
|
||||
rangeIndex?: number;
|
||||
tokenType: SemanticTokenTypes;
|
||||
}
|
||||
export declare function expectSemanticToken(tokensWithRanges: DecodedSemanticTokensWithRanges, options: DecodedTokenOptions): void;
|
||||
export interface CodeActionResult<T extends AstNode = AstNode> extends AsyncDisposable {
|
||||
/** the document containing the AST */
|
||||
document: LangiumDocument<T>;
|
||||
/** all diagnostics of the validation */
|
||||
diagnosticsAll: Diagnostic[];
|
||||
/** the relevant Diagnostic with the given diagnosticCode, it is expected that the given input has exactly one such diagnostic */
|
||||
diagnosticRelevant: Diagnostic;
|
||||
/** the CodeAction to fix the found relevant problem, it is possible, that there is no such code action */
|
||||
action?: CodeAction;
|
||||
}
|
||||
/**
|
||||
* This is a helper function to easily test code actions (quick-fixes) for validation problems.
|
||||
* @param services the Langium services for the language with code actions
|
||||
* @returns A function to easily test a single code action on the given invalid 'input'.
|
||||
* This function expects, that 'input' contains exactly one validation problem with the given 'diagnosticCode'.
|
||||
* If 'outputAfterFix' is specified, this functions checks, that the diagnostic comes with a single code action for this validation problem.
|
||||
* After applying this code action, 'input' is transformed to 'outputAfterFix'.
|
||||
*/
|
||||
export declare function testCodeAction<T extends AstNode = AstNode>(services: LangiumServices): (input: string, diagnosticCode: string, outputAfterFix: string | undefined, options?: ParseHelperOptions) => Promise<CodeActionResult<T>>;
|
||||
//# sourceMappingURL=langium-test.d.ts.map
|
||||
+1
File diff suppressed because one or more lines are too long
+622
@@ -0,0 +1,622 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2021 TypeFox GmbH
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the MIT License, which is available in the project root.
|
||||
******************************************************************************/
|
||||
import * as assert from 'node:assert';
|
||||
import { CodeAction, DiagnosticSeverity, MarkupContent } from 'vscode-languageserver-types';
|
||||
import { normalizeEOL } from '../generate/template-string.js';
|
||||
import { SemanticTokensDecoder } from '../lsp/semantic-token-provider.js';
|
||||
import { Disposable } from '../utils/disposable.js';
|
||||
import { findNodeForProperty } from '../utils/grammar-utils.js';
|
||||
import { escapeRegExp } from '../utils/regexp-utils.js';
|
||||
import { stream } from '../utils/stream.js';
|
||||
import { URI } from '../utils/uri-utils.js';
|
||||
import { DocumentValidator } from '../validation/document-validator.js';
|
||||
import { TextDocument } from '../workspace/documents.js';
|
||||
let nextDocumentId = 1;
|
||||
export function parseHelper(services) {
|
||||
const metaData = services.LanguageMetaData;
|
||||
const documentBuilder = services.shared.workspace.DocumentBuilder;
|
||||
return async (input, options) => {
|
||||
const uri = URI.parse(options?.documentUri ?? `file:///${nextDocumentId++}${metaData.fileExtensions[0] ?? ''}`);
|
||||
const document = services.shared.workspace.LangiumDocumentFactory.fromString(input, uri, options?.parserOptions);
|
||||
services.shared.workspace.LangiumDocuments.addDocument(document);
|
||||
await documentBuilder.build([document], options);
|
||||
return document;
|
||||
};
|
||||
}
|
||||
const expectedFunction = (actual, expected, message) => {
|
||||
assert.deepStrictEqual(actual, expected, message);
|
||||
};
|
||||
/**
|
||||
* Testing utility function for the `textDocument/documentHighlight` LSP request
|
||||
*
|
||||
* @returns A function that performs the assertion
|
||||
*/
|
||||
export function expectHighlight(services) {
|
||||
return async (input) => {
|
||||
const { output, indices, ranges } = replaceIndices(input);
|
||||
const document = await parseDocument(services, output);
|
||||
const highlightProvider = services.lsp.DocumentHighlightProvider;
|
||||
const highlights = await highlightProvider?.getDocumentHighlight(document, textDocumentPositionParams(document, indices[input.index ?? 0])) ?? [];
|
||||
const rangeIndex = input.rangeIndex;
|
||||
if (Array.isArray(rangeIndex)) {
|
||||
expectedFunction(highlights.length, rangeIndex.length, `Expected ${rangeIndex.length} highlights but received ${highlights.length}`);
|
||||
for (let i = 0; i < rangeIndex.length; i++) {
|
||||
const index = rangeIndex[i];
|
||||
const expectedRange = {
|
||||
start: document.textDocument.positionAt(ranges[index][0]),
|
||||
end: document.textDocument.positionAt(ranges[index][1])
|
||||
};
|
||||
const range = highlights[i].range;
|
||||
expectedFunction(range, expectedRange, `Expected range ${rangeToString(expectedRange)} does not match actual range ${rangeToString(range)}`);
|
||||
}
|
||||
}
|
||||
else if (typeof rangeIndex === 'number') {
|
||||
const expectedRange = {
|
||||
start: document.textDocument.positionAt(ranges[rangeIndex][0]),
|
||||
end: document.textDocument.positionAt(ranges[rangeIndex][1])
|
||||
};
|
||||
expectedFunction(highlights.length, 1, `Expected a single highlight but received ${highlights.length}`);
|
||||
const range = highlights[0].range;
|
||||
expectedFunction(range, expectedRange, `Expected range ${rangeToString(expectedRange)} does not match actual range ${rangeToString(range)}`);
|
||||
}
|
||||
else {
|
||||
expectedFunction(highlights.length, ranges.length, `Expected ${ranges.length} highlights but received ${highlights.length}`);
|
||||
for (let i = 0; i < ranges.length; i++) {
|
||||
const range = ranges[i];
|
||||
const expectedRange = {
|
||||
start: document.textDocument.positionAt(range[0]),
|
||||
end: document.textDocument.positionAt(range[1])
|
||||
};
|
||||
const targetRange = highlights[i].range;
|
||||
expectedFunction(targetRange, expectedRange, `Expected range ${rangeToString(expectedRange)} does not match actual range ${rangeToString(targetRange)}`);
|
||||
}
|
||||
}
|
||||
const disposable = Disposable.create(() => clearDocuments(services, [document]));
|
||||
if (input.disposeAfterCheck) {
|
||||
await disposable.dispose();
|
||||
}
|
||||
return disposable;
|
||||
};
|
||||
}
|
||||
export function expectSymbols(services) {
|
||||
return async (input) => {
|
||||
const document = await parseDocument(services, input.text, input.parseOptions);
|
||||
const symbolProvider = services.lsp.DocumentSymbolProvider;
|
||||
const symbols = await symbolProvider?.getSymbols(document, textDocumentParams(document)) ?? [];
|
||||
if ('assert' in input && typeof input.assert === 'function') {
|
||||
input.assert(symbols);
|
||||
}
|
||||
else if ('expectedSymbols' in input) {
|
||||
const symbolToString = input.symbolToString ?? (symbol => symbol.name);
|
||||
const expectedSymbols = input.expectedSymbols;
|
||||
if (symbols.length === expectedSymbols.length) {
|
||||
for (let i = 0; i < expectedSymbols.length; i++) {
|
||||
const expected = expectedSymbols[i];
|
||||
const item = symbols[i];
|
||||
if (typeof expected === 'string') {
|
||||
expectedFunction(symbolToString(item), expected);
|
||||
}
|
||||
else {
|
||||
expectedFunction(item, expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
const symbolsMapped = symbols.map((s, i) => expectedSymbols[i] === undefined || typeof expectedSymbols[i] === 'string' ? symbolToString(s) : s);
|
||||
expectedFunction(symbolsMapped, expectedSymbols, `Expected ${expectedSymbols.length} but found ${symbols.length} symbols in document`);
|
||||
}
|
||||
}
|
||||
const disposable = Disposable.create(() => clearDocuments(services, [document]));
|
||||
if (input.disposeAfterCheck) {
|
||||
await disposable.dispose();
|
||||
}
|
||||
return disposable;
|
||||
};
|
||||
}
|
||||
export function expectWorkspaceSymbols(services) {
|
||||
return async (input) => {
|
||||
const symbolProvider = services.lsp.WorkspaceSymbolProvider;
|
||||
const symbols = await symbolProvider?.getSymbols({
|
||||
query: input.query ?? ''
|
||||
}) ?? [];
|
||||
if ('assert' in input && typeof input.assert === 'function') {
|
||||
input.assert(symbols);
|
||||
}
|
||||
else if ('expectedSymbols' in input) {
|
||||
const symbolToString = input.symbolToString ?? (symbol => symbol.name);
|
||||
const expectedSymbols = input.expectedSymbols;
|
||||
if (symbols.length === expectedSymbols.length) {
|
||||
for (let i = 0; i < expectedSymbols.length; i++) {
|
||||
const expected = expectedSymbols[i];
|
||||
const item = symbols[i];
|
||||
if (typeof expected === 'string') {
|
||||
expectedFunction(symbolToString(item), expected);
|
||||
}
|
||||
else {
|
||||
expectedFunction(item, expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
const symbolsMapped = symbols.map((s, i) => expectedSymbols[i] === undefined || typeof expectedSymbols[i] === 'string' ? symbolToString(s) : s);
|
||||
expectedFunction(symbolsMapped, expectedSymbols, `Expected ${expectedSymbols.length} but found ${symbols.length} symbols in workspace`);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
export function expectFoldings(services) {
|
||||
return async (input) => {
|
||||
const { output, ranges } = replaceIndices(input);
|
||||
const document = await parseDocument(services, output, input.parseOptions);
|
||||
const foldingRangeProvider = services.lsp.FoldingRangeProvider;
|
||||
const foldings = await foldingRangeProvider?.getFoldingRanges(document, textDocumentParams(document)) ?? [];
|
||||
foldings.sort((a, b) => a.startLine - b.startLine);
|
||||
if ('assert' in input && typeof input.assert === 'function') {
|
||||
input.assert(foldings, ranges);
|
||||
}
|
||||
else {
|
||||
expectedFunction(foldings.length, ranges.length, `Expected ${ranges.length} but received ${foldings.length} foldings`);
|
||||
for (let i = 0; i < ranges.length; i++) {
|
||||
const expected = ranges[i];
|
||||
const item = foldings[i];
|
||||
const expectedStart = document.textDocument.positionAt(expected[0]);
|
||||
const expectedEnd = document.textDocument.positionAt(expected[1]);
|
||||
expectedFunction(item.startLine, expectedStart.line, `Expected folding start at line ${expectedStart.line} but received folding start at line ${item.startLine} instead.`);
|
||||
expectedFunction(item.endLine, expectedEnd.line, `Expected folding end at line ${expectedEnd.line} but received folding end at line ${item.endLine} instead.`);
|
||||
}
|
||||
}
|
||||
const disposable = Disposable.create(() => clearDocuments(services, [document]));
|
||||
if (input.disposeAfterCheck) {
|
||||
await disposable.dispose();
|
||||
}
|
||||
return disposable;
|
||||
};
|
||||
}
|
||||
export function textDocumentParams(document) {
|
||||
return { textDocument: { uri: document.textDocument.uri } };
|
||||
}
|
||||
export function expectCompletion(services) {
|
||||
return async (expectedCompletion) => {
|
||||
const { output, indices } = replaceIndices(expectedCompletion);
|
||||
const document = await parseDocument(services, output, expectedCompletion.parseOptions);
|
||||
const completionProvider = services.lsp.CompletionProvider;
|
||||
const offset = indices[expectedCompletion.index];
|
||||
const completions = await completionProvider?.getCompletion(document, textDocumentPositionParams(document, offset)) ?? { isIncomplete: false, items: [] };
|
||||
if ('assert' in expectedCompletion && typeof expectedCompletion.assert === 'function') {
|
||||
expectedCompletion.assert(completions);
|
||||
}
|
||||
else if ('expectedItems' in expectedCompletion) {
|
||||
const itemToString = expectedCompletion.itemToString ?? (completion => completion.label);
|
||||
const expectedItems = expectedCompletion.expectedItems;
|
||||
const items = completions.items.sort((a, b) => a.sortText?.localeCompare(b.sortText || '0') || 0);
|
||||
if (items.length === expectedItems.length) {
|
||||
for (let i = 0; i < expectedItems.length; i++) {
|
||||
const expected = expectedItems[i];
|
||||
const completion = items[i];
|
||||
if (typeof expected === 'string') {
|
||||
expectedFunction(itemToString(completion), expected);
|
||||
}
|
||||
else {
|
||||
expectedFunction(completion, expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
const itemsMapped = items.map((s, i) => expectedItems[i] === undefined || typeof expectedItems[i] === 'string' ? itemToString(s) : s);
|
||||
expectedFunction(itemsMapped, expectedItems, `Expected ${expectedItems.length} but received ${items.length} completion items`);
|
||||
}
|
||||
}
|
||||
const disposable = Disposable.create(() => clearDocuments(services, [document]));
|
||||
if (expectedCompletion.disposeAfterCheck) {
|
||||
await disposable.dispose();
|
||||
}
|
||||
return disposable;
|
||||
};
|
||||
}
|
||||
export function expectGoToDefinition(services) {
|
||||
return async (expectedGoToDefinition) => {
|
||||
const { output, indices, ranges } = replaceIndices(expectedGoToDefinition);
|
||||
const document = await parseDocument(services, output, expectedGoToDefinition.parseOptions);
|
||||
const definitionProvider = services.lsp.DefinitionProvider;
|
||||
const locationLinks = await definitionProvider?.getDefinition(document, textDocumentPositionParams(document, indices[expectedGoToDefinition.index])) ?? [];
|
||||
const rangeIndex = expectedGoToDefinition.rangeIndex;
|
||||
if (Array.isArray(rangeIndex)) {
|
||||
expectedFunction(locationLinks.length, rangeIndex.length, `Expected ${rangeIndex.length} definitions but received ${locationLinks.length}`);
|
||||
for (const index of rangeIndex) {
|
||||
const expectedRange = {
|
||||
start: document.textDocument.positionAt(ranges[index][0]),
|
||||
end: document.textDocument.positionAt(ranges[index][1])
|
||||
};
|
||||
const range = locationLinks[0].targetSelectionRange;
|
||||
expectedFunction(range, expectedRange, `Expected range ${rangeToString(expectedRange)} does not match actual range ${rangeToString(range)}`);
|
||||
}
|
||||
}
|
||||
else {
|
||||
const expectedRange = {
|
||||
start: document.textDocument.positionAt(ranges[rangeIndex][0]),
|
||||
end: document.textDocument.positionAt(ranges[rangeIndex][1])
|
||||
};
|
||||
expectedFunction(locationLinks.length, 1, `Expected a single definition but received ${locationLinks.length}`);
|
||||
const range = locationLinks[0].targetSelectionRange;
|
||||
expectedFunction(range, expectedRange, `Expected range ${rangeToString(expectedRange)} does not match actual range ${rangeToString(range)}`);
|
||||
}
|
||||
const disposable = Disposable.create(() => clearDocuments(services, [document]));
|
||||
if (expectedGoToDefinition.disposeAfterCheck) {
|
||||
await disposable.dispose();
|
||||
}
|
||||
return disposable;
|
||||
};
|
||||
}
|
||||
export function expectFindReferences(services) {
|
||||
return async (expectedFindReferences) => {
|
||||
const { output, indices, ranges } = replaceIndices(expectedFindReferences);
|
||||
const document = await parseDocument(services, output, expectedFindReferences.parseOptions);
|
||||
const expectedRanges = ranges.map(range => ({
|
||||
start: document.textDocument.positionAt(range[0]),
|
||||
end: document.textDocument.positionAt(range[1])
|
||||
}));
|
||||
const referenceFinder = services.lsp.ReferencesProvider;
|
||||
for (const index of indices) {
|
||||
const referenceParameters = referenceParams(document, index, expectedFindReferences.includeDeclaration);
|
||||
const references = await referenceFinder?.findReferences(document, referenceParameters) ?? [];
|
||||
expectedFunction(references.length, expectedRanges.length, 'Found references do not match amount of expected references');
|
||||
for (const reference of references) {
|
||||
expectedFunction(expectedRanges.some(range => isRangeEqual(range, reference.range)), true, `Found unexpected reference at range ${rangeToString(reference.range)}`);
|
||||
}
|
||||
}
|
||||
const disposable = Disposable.create(() => clearDocuments(services, [document]));
|
||||
if (expectedFindReferences.disposeAfterCheck) {
|
||||
await disposable.dispose();
|
||||
}
|
||||
return disposable;
|
||||
};
|
||||
}
|
||||
export function referenceParams(document, offset, includeDeclaration) {
|
||||
return {
|
||||
textDocument: { uri: document.textDocument.uri },
|
||||
position: document.textDocument.positionAt(offset),
|
||||
context: { includeDeclaration }
|
||||
};
|
||||
}
|
||||
export function expectHover(services) {
|
||||
return async (expectedHover) => {
|
||||
const { output, indices } = replaceIndices(expectedHover);
|
||||
const document = await parseDocument(services, output, expectedHover.parseOptions);
|
||||
const hoverProvider = services.lsp.HoverProvider;
|
||||
const hover = await hoverProvider?.getHoverContent(document, textDocumentPositionParams(document, indices[expectedHover.index]));
|
||||
const hoverContent = hover && MarkupContent.is(hover.contents) ? hover.contents.value : undefined;
|
||||
if (typeof expectedHover.hover !== 'object') {
|
||||
expectedFunction(hoverContent, expectedHover.hover);
|
||||
}
|
||||
else {
|
||||
const value = hoverContent ?? '';
|
||||
expectedFunction(expectedHover.hover.test(value), true, `Hover '${value}' does not match regex /${expectedHover.hover.source}/${expectedHover.hover.flags}.`);
|
||||
}
|
||||
const disposable = Disposable.create(() => clearDocuments(services, [document]));
|
||||
if (expectedHover.disposeAfterCheck) {
|
||||
await disposable.dispose();
|
||||
}
|
||||
return disposable;
|
||||
};
|
||||
}
|
||||
export function expectFormatting(services) {
|
||||
const formatter = services.lsp.Formatter;
|
||||
if (!formatter) {
|
||||
throw new Error(`No formatter registered for language ${services.LanguageMetaData.languageId}`);
|
||||
}
|
||||
return async (expectedFormatting) => {
|
||||
const document = await parseDocument(services, expectedFormatting.before, expectedFormatting.parseOptions);
|
||||
const identifier = { uri: document.uri.toString() };
|
||||
const options = expectedFormatting.options ?? {
|
||||
insertSpaces: true,
|
||||
tabSize: 4
|
||||
};
|
||||
const edits = await (expectedFormatting.range ?
|
||||
formatter.formatDocumentRange(document, { options, textDocument: identifier, range: expectedFormatting.range }) :
|
||||
formatter.formatDocument(document, { options, textDocument: identifier }));
|
||||
const editedDocument = TextDocument.applyEdits(document.textDocument, edits);
|
||||
expectedFunction(normalizeEOL(editedDocument), normalizeEOL(expectedFormatting.after));
|
||||
const disposable = Disposable.create(() => clearDocuments(services, [document]));
|
||||
if (expectedFormatting.disposeAfterCheck) {
|
||||
await disposable.dispose();
|
||||
}
|
||||
return disposable;
|
||||
};
|
||||
}
|
||||
export function textDocumentPositionParams(document, offset) {
|
||||
return { textDocument: { uri: document.textDocument.uri }, position: document.textDocument.positionAt(offset) };
|
||||
}
|
||||
export async function parseDocument(services, input, options) {
|
||||
const document = await parseHelper(services)(input, options);
|
||||
if (!document.parseResult) {
|
||||
throw new Error('Could not parse document');
|
||||
}
|
||||
return document;
|
||||
}
|
||||
export function replaceIndices(base) {
|
||||
const indices = [];
|
||||
const ranges = [];
|
||||
const rangeStack = [];
|
||||
const indexMarker = base.indexMarker || '<|>';
|
||||
const rangeStartMarker = base.rangeStartMarker || '<|';
|
||||
const rangeEndMarker = base.rangeEndMarker || '|>';
|
||||
const regex = new RegExp(`${escapeRegExp(indexMarker)}|${escapeRegExp(rangeStartMarker)}|${escapeRegExp(rangeEndMarker)}`);
|
||||
let matched = true;
|
||||
let input = base.text;
|
||||
while (matched) {
|
||||
const regexMatch = regex.exec(input);
|
||||
if (regexMatch) {
|
||||
const matchedString = regexMatch[0];
|
||||
switch (matchedString) {
|
||||
case indexMarker:
|
||||
indices.push(regexMatch.index);
|
||||
break;
|
||||
case rangeStartMarker:
|
||||
rangeStack.push(regexMatch.index);
|
||||
break;
|
||||
case rangeEndMarker: {
|
||||
const rangeStart = rangeStack.pop() || 0;
|
||||
ranges.push([rangeStart, regexMatch.index]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
input = input.substring(0, regexMatch.index) + input.substring(regexMatch.index + matchedString.length);
|
||||
}
|
||||
else {
|
||||
matched = false;
|
||||
}
|
||||
}
|
||||
return { output: input, indices, ranges: ranges.sort((a, b) => a[0] - b[0]) };
|
||||
}
|
||||
export function validationHelper(services) {
|
||||
const parse = parseHelper(services);
|
||||
return async (input, options) => {
|
||||
const parseOptions = {
|
||||
...(options ?? {}),
|
||||
};
|
||||
parseOptions.validation ?? (parseOptions.validation = true);
|
||||
const document = await parse(input, parseOptions);
|
||||
const result = {
|
||||
document,
|
||||
diagnostics: document.diagnostics ?? [],
|
||||
dispose: () => clearDocuments(services, [document])
|
||||
};
|
||||
if (options?.failOnParsingErrors) {
|
||||
expectNoIssues(result, {
|
||||
severity: DiagnosticSeverity.Error,
|
||||
data: {
|
||||
code: DocumentValidator.ParsingError,
|
||||
},
|
||||
});
|
||||
}
|
||||
return result;
|
||||
};
|
||||
}
|
||||
export function isDiagnosticDataEqual(lhs, rhs) {
|
||||
if (lhs === rhs) {
|
||||
return true;
|
||||
}
|
||||
if (typeof lhs === 'object' && lhs !== null && typeof rhs === 'object' && rhs !== null) {
|
||||
for (const key of Object.keys(rhs)) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
if (!isDiagnosticDataEqual(lhs[key], rhs[key])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
export function isRangeEqual(lhs, rhs) {
|
||||
return lhs.start.character === rhs.start.character
|
||||
&& lhs.start.line === rhs.start.line
|
||||
&& lhs.end.character === rhs.end.character
|
||||
&& lhs.end.line === rhs.end.line;
|
||||
}
|
||||
export function rangeToString(range) {
|
||||
return `${range.start.line}:${range.start.character}--${range.end.line}:${range.end.character}`;
|
||||
}
|
||||
export function filterByOptions(validationResult, options) {
|
||||
const filters = [];
|
||||
if ('node' in options && options.node) {
|
||||
let cstNode = options.node.$cstNode;
|
||||
if (options.property) {
|
||||
const name = typeof options.property === 'string' ? options.property : options.property.name;
|
||||
const index = typeof options.property === 'string' ? undefined : options.property.index;
|
||||
cstNode = findNodeForProperty(cstNode, name, index);
|
||||
}
|
||||
if (!cstNode) {
|
||||
throw new Error('Cannot find the node!');
|
||||
}
|
||||
filters.push(d => isRangeEqual(cstNode.range, d.range));
|
||||
}
|
||||
if ('offset' in options) {
|
||||
const outer = {
|
||||
start: validationResult.document.textDocument.positionAt(options.offset),
|
||||
end: validationResult.document.textDocument.positionAt(options.offset + options.length)
|
||||
};
|
||||
filters.push(d => isRangeEqual(outer, d.range));
|
||||
}
|
||||
if ('range' in options) {
|
||||
filters.push(d => isRangeEqual(options.range, d.range));
|
||||
}
|
||||
if (options.code) {
|
||||
filters.push(d => d.code === options.code);
|
||||
}
|
||||
if (options.data) {
|
||||
filters.push(d => isDiagnosticDataEqual(d.data, options.data));
|
||||
}
|
||||
if (options.message) {
|
||||
if (typeof options.message === 'string') {
|
||||
filters.push(d => d.message === options.message);
|
||||
}
|
||||
else if (options.message instanceof RegExp) {
|
||||
const regexp = options.message;
|
||||
filters.push(d => regexp.test(d.message));
|
||||
}
|
||||
}
|
||||
if (options.severity) {
|
||||
filters.push(d => d.severity === options.severity);
|
||||
}
|
||||
return validationResult.diagnostics.filter(diag => filters.every(holdsFor => holdsFor(diag)));
|
||||
}
|
||||
export function expectNoIssues(validationResult, filterOptions) {
|
||||
const filtered = filterOptions ? filterByOptions(validationResult, filterOptions) : validationResult.diagnostics;
|
||||
expectedFunction(filtered.length, 0, `Expected no issues in '${validationResult.document.uri.toString()}', but found ${filtered.length}:\n${printDiagnostics(filtered)}`);
|
||||
}
|
||||
export function expectIssue(validationResult, filterOptions) {
|
||||
const filtered = filterOptions ? filterByOptions(validationResult, filterOptions) : validationResult.diagnostics;
|
||||
expectedFunction(filtered.length > 0, true, `Found no issues in '${validationResult.document.uri.toString()}'`);
|
||||
}
|
||||
export function expectError(validationResult, message, filterOptions) {
|
||||
const content = {
|
||||
message,
|
||||
severity: DiagnosticSeverity.Error
|
||||
};
|
||||
expectIssue(validationResult, {
|
||||
...filterOptions,
|
||||
...content,
|
||||
});
|
||||
}
|
||||
export function expectWarning(validationResult, message, filterOptions) {
|
||||
const content = {
|
||||
message,
|
||||
severity: DiagnosticSeverity.Warning
|
||||
};
|
||||
expectIssue(validationResult, {
|
||||
...filterOptions,
|
||||
...content,
|
||||
});
|
||||
}
|
||||
export function printDiagnostics(diagnostics) {
|
||||
return diagnostics?.map(d => `line ${d.range.start.line}, column ${d.range.start.character}: ${d.message}`).join('\n') ?? '';
|
||||
}
|
||||
/**
|
||||
* Add the given document to the `TextDocuments` service, simulating it being opened in an editor.
|
||||
*
|
||||
* @deprecated Since 3.2.0. Use `set`/`delete` from `TextDocuments` instead.
|
||||
*/
|
||||
export function setTextDocument(services, document) {
|
||||
const shared = 'shared' in services ? services.shared : services;
|
||||
const textDocuments = shared.workspace.TextDocuments;
|
||||
textDocuments.set(document);
|
||||
return Disposable.create(() => {
|
||||
textDocuments.delete(document.uri);
|
||||
});
|
||||
}
|
||||
export function clearDocuments(services, documents) {
|
||||
const shared = 'shared' in services ? services.shared : services;
|
||||
const allDocs = (documents ? stream(documents) : shared.workspace.LangiumDocuments.all).map(x => x.uri).toArray();
|
||||
return shared.workspace.DocumentBuilder.update([], allDocs);
|
||||
}
|
||||
export function highlightHelper(services) {
|
||||
const parse = parseHelper(services);
|
||||
const tokenProvider = services.lsp.SemanticTokenProvider;
|
||||
if (!tokenProvider) {
|
||||
throw new Error('No semantic token provider provided!');
|
||||
}
|
||||
return async (text, options) => {
|
||||
const { output: input, ranges } = replaceIndices({
|
||||
text
|
||||
});
|
||||
const document = await parse(input, options);
|
||||
const params = { textDocument: { uri: document.textDocument.uri } };
|
||||
const tokens = await tokenProvider.semanticHighlight(document, params);
|
||||
return { tokens: SemanticTokensDecoder.decode(tokens, tokenProvider.tokenTypes, document), ranges };
|
||||
};
|
||||
}
|
||||
export function expectSemanticToken(tokensWithRanges, options) {
|
||||
const range = tokensWithRanges.ranges[options.rangeIndex || 0];
|
||||
const result = tokensWithRanges.tokens.filter(t => {
|
||||
return t.tokenType === options.tokenType && t.offset === range[0] && t.offset + t.text.length === range[1];
|
||||
});
|
||||
expectedFunction(result.length, 1, `Expected one token with the specified options but found ${result.length}`);
|
||||
}
|
||||
/**
|
||||
* This is a helper function to easily test code actions (quick-fixes) for validation problems.
|
||||
* @param services the Langium services for the language with code actions
|
||||
* @returns A function to easily test a single code action on the given invalid 'input'.
|
||||
* This function expects, that 'input' contains exactly one validation problem with the given 'diagnosticCode'.
|
||||
* If 'outputAfterFix' is specified, this functions checks, that the diagnostic comes with a single code action for this validation problem.
|
||||
* After applying this code action, 'input' is transformed to 'outputAfterFix'.
|
||||
*/
|
||||
export function testCodeAction(services) {
|
||||
const validateHelper = validationHelper(services);
|
||||
return async (input, diagnosticCode, outputAfterFix, options) => {
|
||||
// parse + validate
|
||||
const validationBefore = await validateHelper(input, options);
|
||||
const document = validationBefore.document;
|
||||
const diagnosticsAll = document.diagnostics ?? [];
|
||||
// use only the diagnostics with the given validation code
|
||||
const diagnosticsRelevant = diagnosticsAll.filter(d => d.data && 'code' in d.data && d.data.code === diagnosticCode);
|
||||
// expect exactly one validation with the given code
|
||||
expectedFunction(diagnosticsRelevant.length, 1);
|
||||
const diagnosticRelevant = diagnosticsRelevant[0];
|
||||
// check, that the code actions are generated for the selected validation:
|
||||
// prepare the action provider
|
||||
const actionProvider = expectTruthy(services.lsp.CodeActionProvider);
|
||||
// request the actions for this diagnostic
|
||||
const currentActions = await actionProvider.getCodeActions(document, {
|
||||
...textDocumentParams(document),
|
||||
range: diagnosticRelevant.range,
|
||||
context: {
|
||||
diagnostics: diagnosticsRelevant,
|
||||
triggerKind: 1 // explicitly triggered by users (or extensions)
|
||||
}
|
||||
});
|
||||
// evaluate the resulting actions
|
||||
let action;
|
||||
let validationAfter;
|
||||
if (outputAfterFix) {
|
||||
// exactly one code action is expected
|
||||
expectTruthy(currentActions);
|
||||
expectTruthy(Array.isArray(currentActions));
|
||||
expectedFunction(currentActions.length, 1);
|
||||
expectTruthy(CodeAction.is(currentActions[0]));
|
||||
action = currentActions[0];
|
||||
// execute the found code action
|
||||
const edits = expectTruthy(action.edit?.changes[document.textDocument.uri]);
|
||||
const updatedText = TextDocument.applyEdits(document.textDocument, edits);
|
||||
// check the result after applying the code action:
|
||||
// 1st text is updated as expected
|
||||
expectedFunction(updatedText, outputAfterFix);
|
||||
// 2nd the validation diagnostic is gone after applying the code action
|
||||
validationAfter = await validateHelper(updatedText, options);
|
||||
const diagnosticsUpdated = validationAfter.diagnostics.filter(d => d.data && 'code' in d.data && d.data.code === diagnosticCode);
|
||||
expectedFunction(diagnosticsUpdated.length, 0);
|
||||
}
|
||||
else {
|
||||
// no code action is expected
|
||||
expectFalsy(currentActions);
|
||||
}
|
||||
// collect the data to return
|
||||
async function dispose() {
|
||||
validationBefore.dispose();
|
||||
validationAfter?.dispose();
|
||||
}
|
||||
return {
|
||||
document,
|
||||
diagnosticsAll,
|
||||
diagnosticRelevant: diagnosticRelevant,
|
||||
action,
|
||||
dispose: () => dispose()
|
||||
};
|
||||
};
|
||||
}
|
||||
function expectTruthy(value) {
|
||||
if (value) {
|
||||
return value;
|
||||
}
|
||||
else {
|
||||
throw new Error();
|
||||
}
|
||||
}
|
||||
function expectFalsy(value) {
|
||||
if (value) {
|
||||
throw new Error();
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=langium-test.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
+23
@@ -0,0 +1,23 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2025 TypeFox GmbH
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the MIT License, which is available in the project root.
|
||||
******************************************************************************/
|
||||
import { URI } from 'vscode-uri';
|
||||
import type { FileSystemNode, FileSystemProvider } from '../workspace/file-system-provider.js';
|
||||
export declare class VirtualFileSystemProvider implements FileSystemProvider {
|
||||
private readonly trie;
|
||||
insert(uri: URI | string, content: string): void;
|
||||
delete(uri: URI | string): void;
|
||||
stat(uri: URI): Promise<FileSystemNode>;
|
||||
statSync(uri: URI): FileSystemNode;
|
||||
exists(uri: URI): Promise<boolean>;
|
||||
existsSync(uri: URI): boolean;
|
||||
readBinary(uri: URI): Promise<Uint8Array>;
|
||||
readBinarySync(uri: URI): Uint8Array;
|
||||
readFile(uri: URI): Promise<string>;
|
||||
readFileSync(uri: URI): string;
|
||||
readDirectory(uri: URI): Promise<FileSystemNode[]>;
|
||||
readDirectorySync(uri: URI): FileSystemNode[];
|
||||
}
|
||||
//# sourceMappingURL=virtual-file-system.d.ts.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"virtual-file-system.d.ts","sourceRoot":"","sources":["../../src/test/virtual-file-system.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAEhF,OAAO,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AACjC,OAAO,KAAK,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,sCAAsC,CAAC;AAG/F,qBAAa,yBAA0B,YAAW,kBAAkB;IAEhE,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAyB;IAE9C,MAAM,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAIhD,MAAM,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,GAAG,IAAI;IAI/B,IAAI,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,cAAc,CAAC;IAIvC,QAAQ,CAAC,GAAG,EAAE,GAAG,GAAG,cAAc;IAalC,MAAM,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC;IAIlC,UAAU,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO;IAG7B,UAAU,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,UAAU,CAAC;IAIzC,cAAc,CAAC,GAAG,EAAE,GAAG,GAAG,UAAU;IAKpC,QAAQ,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,MAAM,CAAC;IAInC,YAAY,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM;IAS9B,aAAa,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;IAIlD,iBAAiB,CAAC,GAAG,EAAE,GAAG,GAAG,cAAc,EAAE;CAahD"}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2025 TypeFox GmbH
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the MIT License, which is available in the project root.
|
||||
******************************************************************************/
|
||||
import { URI } from 'vscode-uri';
|
||||
import { UriTrie } from '../utils/uri-utils.js';
|
||||
export class VirtualFileSystemProvider {
|
||||
constructor() {
|
||||
this.trie = new UriTrie();
|
||||
}
|
||||
insert(uri, content) {
|
||||
this.trie.insert(uri, content);
|
||||
}
|
||||
delete(uri) {
|
||||
this.trie.delete(uri);
|
||||
}
|
||||
stat(uri) {
|
||||
return Promise.resolve(this.statSync(uri));
|
||||
}
|
||||
statSync(uri) {
|
||||
const node = this.trie.findNode(uri);
|
||||
if (node) {
|
||||
return {
|
||||
isDirectory: node.element === undefined,
|
||||
isFile: node.element !== undefined,
|
||||
uri
|
||||
};
|
||||
}
|
||||
else {
|
||||
throw new Error('File not found');
|
||||
}
|
||||
}
|
||||
exists(uri) {
|
||||
return Promise.resolve(this.existsSync(uri));
|
||||
}
|
||||
existsSync(uri) {
|
||||
return this.trie.findNode(uri) !== undefined;
|
||||
}
|
||||
readBinary(uri) {
|
||||
return Promise.resolve(this.readBinarySync(uri));
|
||||
}
|
||||
readBinarySync(uri) {
|
||||
const encoder = new TextEncoder();
|
||||
return encoder.encode(this.readFileSync(uri));
|
||||
}
|
||||
readFile(uri) {
|
||||
return Promise.resolve(this.readFileSync(uri));
|
||||
}
|
||||
readFileSync(uri) {
|
||||
const data = this.trie.find(uri);
|
||||
if (typeof data === 'string') {
|
||||
return data;
|
||||
}
|
||||
else {
|
||||
throw new Error('File not found');
|
||||
}
|
||||
}
|
||||
readDirectory(uri) {
|
||||
return Promise.resolve(this.readDirectorySync(uri));
|
||||
}
|
||||
readDirectorySync(uri) {
|
||||
const node = this.trie.findNode(uri);
|
||||
if (!node) {
|
||||
throw new Error('Directory not found');
|
||||
}
|
||||
const children = this.trie.findChildren(uri);
|
||||
return children.map(child => ({
|
||||
isDirectory: child.element === undefined,
|
||||
isFile: child.element !== undefined,
|
||||
uri: URI.parse(child.uri)
|
||||
}));
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=virtual-file-system.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"virtual-file-system.js","sourceRoot":"","sources":["../../src/test/virtual-file-system.ts"],"names":[],"mappings":"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"}
|
||||
Reference in New Issue
Block a user