First commit.

Signed-off-by: Chen Xiao <abigwc@gmail.com>
This commit is contained in:
Chen Xiao
2026-05-08 14:43:16 +08:00
commit 0b64e2de94
10989 changed files with 2253791 additions and 0 deletions
+103
View File
@@ -0,0 +1,103 @@
/******************************************************************************
* 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 { Range } from 'vscode-languageserver-types';
import type { AstNode, AstReflection, CstNode, MultiReference, Reference, ReferenceInfo } from '../syntax-tree.js';
import type { Stream, TreeStream } from './stream.js';
import type { LangiumDocument } from '../workspace/documents.js';
/**
* Link the `$container` and other related properties of every AST node that is directly contained
* in the given `node`.
*/
export declare function linkContentToContainer(node: AstNode, options?: {
/**
* If true, the function will also link the content of the contained nodes.
* Otherwise, only the immediate children of the given node are linked to their container.
*/
deep?: boolean;
}): void;
/**
* Walk along the hierarchy of containers from the given AST node to the root and return the first
* node that matches the type predicate. If the start node itself matches, it is returned.
* If no container matches, `undefined` is returned.
*/
export declare function getContainerOfType<T extends AstNode>(node: AstNode | undefined, typePredicate: (n: AstNode) => n is T): T | undefined;
/**
* Walk along the hierarchy of containers from the given AST node to the root and check for existence
* of a container that matches the given predicate. The start node is included in the checks.
*/
export declare function hasContainerOfType(node: AstNode | undefined, predicate: (n: AstNode) => boolean): boolean;
/**
* Retrieve the document in which the given AST node is contained. A reference to the document is
* usually held by the root node of the AST.
*
* @throws an error if the node is not contained in a document.
*/
export declare function getDocument<T extends AstNode = AstNode>(node: AstNode): LangiumDocument<T>;
/**
* Returns the root node of the given AST node by following the `$container` references.
*/
export declare function findRootNode(node: AstNode): AstNode;
/**
* Returns all AST nodes that are referenced by the given reference or multi-reference.
*/
export declare function getReferenceNodes(reference: Reference | MultiReference): AstNode[];
export interface AstStreamOptions {
/**
* Optional target range that the nodes in the stream need to intersect
*/
range?: Range;
}
/**
* Create a stream of all AST nodes that are directly contained in the given node. This includes
* single-valued as well as multi-valued (array) properties.
*/
export declare function streamContents(node: AstNode, options?: AstStreamOptions): Stream<AstNode>;
/**
* Create a stream of all AST nodes that are directly and indirectly contained in the given root node.
* This does not include the root node itself.
*/
export declare function streamAllContents(root: AstNode, options?: AstStreamOptions): TreeStream<AstNode>;
/**
* Create a stream of all AST nodes that are directly and indirectly contained in the given root node,
* including the root node itself.
*/
export declare function streamAst(root: AstNode, options?: AstStreamOptions): TreeStream<AstNode>;
/**
* Create a stream of all cross-references that are held by the given AST node. This includes
* single-valued as well as multi-valued (array) properties.
*/
export declare function streamReferences(node: AstNode): Stream<ReferenceInfo>;
/**
* Assigns all mandatory AST properties to the specified node.
*
* @param reflection Reflection object used to gather mandatory properties for the node.
* @param node Specified node is modified in place and properties are directly assigned.
*/
export declare function assignMandatoryProperties(reflection: AstReflection, node: AstNode): void;
/**
* Creates a deep copy of the specified AST node.
* The resulting copy will only contain semantically relevant information, such as the `$type` property and AST properties.
*
* @param node The AST node to deeply copy.
* @param buildReference References are not copied, instead this function is called to rebuild them.
* @param trace For the sake of tracking copied nodes and their originals a `trace` map can be provided (optional).
*/
export declare function copyAstNode<T extends AstNode = AstNode>(node: T, buildReference: (node: AstNode, property: string, refNode: CstNode | undefined, refText: string, origReference: Reference<AstNode>) => Reference<AstNode>, trace?: Map<AstNode, AstNode>): T;
/**
* Recursively makes all properties of an AstNode optional, except for those
* that start with a dollar sign ($) or are of type boolean or are of type array.
* If the type is a Reference or an Array, it applies the transformation recursively
* to the inner type.
* Otherwise the type is returned as is.
*
* @template T - The type to be transformed.
*/
export type DeepPartialAstNode<T> = T extends Reference<infer U extends AstNode> ? Reference<DeepPartialAstNode<U>> : T extends AstNode ? {
[K in keyof T as K extends `$${string}` | (T[K] extends (boolean | any[]) ? K : never) ? K : never]: DeepPartialAstNode<T[K]>;
} & {
[K in keyof T as K extends `$${string}` ? never : T[K] extends (boolean | any[]) ? never : K]?: DeepPartialAstNode<T[K]>;
} : T extends Array<infer U> ? Array<DeepPartialAstNode<U>> : T;
//# sourceMappingURL=ast-utils.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"ast-utils.d.ts","sourceRoot":"","sources":["../../src/utils/ast-utils.ts"],"names":[],"mappings":"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"}
+279
View File
@@ -0,0 +1,279 @@
/******************************************************************************
* 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 { isAstNode, isMultiReference, isReference } from '../syntax-tree.js';
import { DONE_RESULT, StreamImpl, TreeStreamImpl } from './stream.js';
import { inRange } from './cst-utils.js';
/**
* Link the `$container` and other related properties of every AST node that is directly contained
* in the given `node`.
*/
export function linkContentToContainer(node, options = {}) {
for (const [name, value] of Object.entries(node)) {
if (!name.startsWith('$')) {
if (Array.isArray(value)) {
value.forEach((item, index) => {
if (isAstNode(item)) {
item.$container = node;
item.$containerProperty = name;
item.$containerIndex = index;
if (options.deep) {
linkContentToContainer(item, options);
}
}
});
}
else if (isAstNode(value)) {
value.$container = node;
value.$containerProperty = name;
if (options.deep) {
linkContentToContainer(value, options);
}
}
}
}
}
/**
* Walk along the hierarchy of containers from the given AST node to the root and return the first
* node that matches the type predicate. If the start node itself matches, it is returned.
* If no container matches, `undefined` is returned.
*/
export function getContainerOfType(node, typePredicate) {
let item = node;
while (item) {
if (typePredicate(item)) {
return item;
}
item = item.$container;
}
return undefined;
}
/**
* Walk along the hierarchy of containers from the given AST node to the root and check for existence
* of a container that matches the given predicate. The start node is included in the checks.
*/
export function hasContainerOfType(node, predicate) {
let item = node;
while (item) {
if (predicate(item)) {
return true;
}
item = item.$container;
}
return false;
}
/**
* Retrieve the document in which the given AST node is contained. A reference to the document is
* usually held by the root node of the AST.
*
* @throws an error if the node is not contained in a document.
*/
export function getDocument(node) {
const rootNode = findRootNode(node);
const result = rootNode.$document;
if (!result) {
throw new Error('AST node has no document.');
}
return result;
}
/**
* Returns the root node of the given AST node by following the `$container` references.
*/
export function findRootNode(node) {
while (node.$container) {
node = node.$container;
}
return node;
}
/**
* Returns all AST nodes that are referenced by the given reference or multi-reference.
*/
export function getReferenceNodes(reference) {
if (isReference(reference)) {
return reference.ref ? [reference.ref] : [];
}
else if (isMultiReference(reference)) {
return reference.items.map(item => item.ref);
}
return [];
}
/**
* Create a stream of all AST nodes that are directly contained in the given node. This includes
* single-valued as well as multi-valued (array) properties.
*/
export function streamContents(node, options) {
if (!node) {
throw new Error('Node must be an AstNode.');
}
const range = options?.range;
return new StreamImpl(() => ({
keys: Object.keys(node),
keyIndex: 0,
arrayIndex: 0
}), state => {
while (state.keyIndex < state.keys.length) {
const property = state.keys[state.keyIndex];
if (!property.startsWith('$')) {
const value = node[property];
if (isAstNode(value)) {
state.keyIndex++;
if (isAstNodeInRange(value, range)) {
return { done: false, value };
}
}
else if (Array.isArray(value)) {
while (state.arrayIndex < value.length) {
const index = state.arrayIndex++;
const element = value[index];
if (isAstNode(element) && isAstNodeInRange(element, range)) {
return { done: false, value: element };
}
}
state.arrayIndex = 0;
}
}
state.keyIndex++;
}
return DONE_RESULT;
});
}
/**
* Create a stream of all AST nodes that are directly and indirectly contained in the given root node.
* This does not include the root node itself.
*/
export function streamAllContents(root, options) {
if (!root) {
throw new Error('Root node must be an AstNode.');
}
return new TreeStreamImpl(root, node => streamContents(node, options));
}
/**
* Create a stream of all AST nodes that are directly and indirectly contained in the given root node,
* including the root node itself.
*/
export function streamAst(root, options) {
if (!root) {
throw new Error('Root node must be an AstNode.');
}
else if (options?.range && !isAstNodeInRange(root, options.range)) {
// Return an empty stream if the root node isn't in range
return new TreeStreamImpl(root, () => []);
}
return new TreeStreamImpl(root, node => streamContents(node, options), { includeRoot: true });
}
function isAstNodeInRange(astNode, range) {
if (!range) {
return true;
}
const nodeRange = astNode.$cstNode?.range;
if (!nodeRange) {
return false;
}
return inRange(nodeRange, range);
}
/**
* Create a stream of all cross-references that are held by the given AST node. This includes
* single-valued as well as multi-valued (array) properties.
*/
export function streamReferences(node) {
return new StreamImpl(() => ({
keys: Object.keys(node),
keyIndex: 0,
arrayIndex: 0
}), state => {
while (state.keyIndex < state.keys.length) {
const property = state.keys[state.keyIndex];
if (!property.startsWith('$')) {
const value = node[property];
if (isReference(value) || isMultiReference(value)) {
state.keyIndex++;
return { done: false, value: { reference: value, container: node, property } };
}
else if (Array.isArray(value)) {
while (state.arrayIndex < value.length) {
const index = state.arrayIndex++;
const element = value[index];
if (isReference(element) || isMultiReference(value)) {
return { done: false, value: { reference: element, container: node, property, index } };
}
}
state.arrayIndex = 0;
}
}
state.keyIndex++;
}
return DONE_RESULT;
});
}
/**
* Assigns all mandatory AST properties to the specified node.
*
* @param reflection Reflection object used to gather mandatory properties for the node.
* @param node Specified node is modified in place and properties are directly assigned.
*/
export function assignMandatoryProperties(reflection, node) {
const typeMetaData = reflection.getTypeMetaData(node.$type);
const genericNode = node;
for (const property of Object.values(typeMetaData.properties)) {
// Only set the value if the property is not already set and if it has a default value
if (property.defaultValue !== undefined && genericNode[property.name] === undefined) {
genericNode[property.name] = copyDefaultValue(property.defaultValue);
}
}
}
function copyDefaultValue(propertyType) {
if (Array.isArray(propertyType)) {
return [...propertyType.map(copyDefaultValue)];
}
else {
return propertyType;
}
}
/**
* Creates a deep copy of the specified AST node.
* The resulting copy will only contain semantically relevant information, such as the `$type` property and AST properties.
*
* @param node The AST node to deeply copy.
* @param buildReference References are not copied, instead this function is called to rebuild them.
* @param trace For the sake of tracking copied nodes and their originals a `trace` map can be provided (optional).
*/
export function copyAstNode(node, buildReference, trace) {
const copy = { $type: node.$type };
if (trace) {
trace.set(node, copy);
trace.set(copy, node);
}
for (const [name, value] of Object.entries(node)) {
if (!name.startsWith('$')) {
if (isAstNode(value)) {
copy[name] = copyAstNode(value, buildReference, trace);
}
else if (isReference(value)) {
copy[name] = buildReference(copy, name, value.$refNode, value.$refText, value);
}
else if (Array.isArray(value)) {
const copiedArray = [];
for (const element of value) {
if (isAstNode(element)) {
copiedArray.push(copyAstNode(element, buildReference, trace));
}
else if (isReference(element)) {
copiedArray.push(buildReference(copy, name, element.$refNode, element.$refText, element));
}
else {
copiedArray.push(element);
}
}
copy[name] = copiedArray;
}
else {
copy[name] = value;
}
}
}
linkContentToContainer(copy, { deep: true });
return copy;
}
//# sourceMappingURL=ast-utils.js.map
File diff suppressed because one or more lines are too long
+74
View File
@@ -0,0 +1,74 @@
/******************************************************************************
* Copyright 2023 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 { Disposable } from './disposable.js';
import type { URI } from './uri-utils.js';
import type { LangiumSharedCoreServices } from '../services.js';
import type { DocumentState } from '../workspace/documents.js';
export declare abstract class DisposableCache implements Disposable {
protected toDispose: Disposable[];
protected isDisposed: boolean;
onDispose(disposable: Disposable): void;
dispose(): void;
protected throwIfDisposed(): void;
abstract clear(): void;
}
export declare class SimpleCache<K, V> extends DisposableCache {
protected readonly cache: Map<K, V>;
has(key: K): boolean;
set(key: K, value: V): void;
get(key: K): V | undefined;
get(key: K, provider: () => V): V;
delete(key: K): boolean;
clear(): void;
}
export declare class ContextCache<Context, Key, Value, ContextKey = Context> extends DisposableCache {
private readonly cache;
private readonly converter;
constructor(converter?: (input: Context) => ContextKey);
has(contextKey: Context, key: Key): boolean;
set(contextKey: Context, key: Key, value: Value): void;
get(contextKey: Context, key: Key): Value | undefined;
get(contextKey: Context, key: Key, provider: () => Value): Value;
delete(contextKey: Context, key: Key): boolean;
clear(): void;
clear(contextKey: Context): void;
protected cacheForContext(contextKey: Context): Map<Key, Value>;
}
/**
* Every key/value pair in this cache is scoped to a document.
* If this document is changed or deleted, all associated key/value pairs are deleted.
*/
export declare class DocumentCache<K, V> extends ContextCache<URI | string, K, V, string> {
/**
* Creates a new document cache.
*
* @param sharedServices Service container instance to hook into document lifecycle events.
* @param state Optional document state on which the cache should evict.
* If not provided, the cache will evict on `DocumentBuilder#onUpdate`.
* *Deleted* documents are considered in both cases.
*
* Providing a state here will use `DocumentBuilder#onDocumentPhase` instead,
* which triggers on all documents that have been affected by this change, assuming that the
* state is `DocumentState.Linked` or a later state.
*/
constructor(sharedServices: LangiumSharedCoreServices, state?: DocumentState);
}
/**
* Every key/value pair in this cache is scoped to the whole workspace.
* If any document in the workspace is added, changed or deleted, the whole cache is evicted.
*/
export declare class WorkspaceCache<K, V> extends SimpleCache<K, V> {
/**
* Creates a new workspace cache.
*
* @param sharedServices Service container instance to hook into document lifecycle events.
* @param state Optional document state on which the cache should evict.
* If not provided, the cache will evict on `DocumentBuilder#onUpdate`.
* *Deleted* documents are considered in both cases.
*/
constructor(sharedServices: LangiumSharedCoreServices, state?: DocumentState);
}
//# sourceMappingURL=caching.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"caching.d.ts","sourceRoot":"","sources":["../../src/utils/caching.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAEhF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,gBAAgB,CAAC;AAChE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAE/D,8BAAsB,eAAgB,YAAW,UAAU;IAEvD,SAAS,CAAC,SAAS,EAAE,UAAU,EAAE,CAAM;IACvC,SAAS,CAAC,UAAU,UAAS;IAE7B,SAAS,CAAC,UAAU,EAAE,UAAU,GAAG,IAAI;IAIvC,OAAO,IAAI,IAAI;IAOf,SAAS,CAAC,eAAe,IAAI,IAAI;IAMjC,QAAQ,CAAC,KAAK,IAAI,IAAI;CACzB;AAED,qBAAa,WAAW,CAAC,CAAC,EAAE,CAAC,CAAE,SAAQ,eAAe;IAClD,SAAS,CAAC,QAAQ,CAAC,KAAK,YAAmB;IAE3C,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,OAAO;IAKpB,GAAG,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI;IAK3B,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG,SAAS;IAC1B,GAAG,CAAC,GAAG,EAAE,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,GAAG,CAAC;IAcjC,MAAM,CAAC,GAAG,EAAE,CAAC,GAAG,OAAO;IAKvB,KAAK,IAAI,IAAI;CAIhB;AAED,qBAAa,YAAY,CAAC,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,UAAU,GAAG,OAAO,CAAE,SAAQ,eAAe;IAExF,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAoD;IAC1E,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA2C;gBAEzD,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,UAAU;IAKtD,GAAG,CAAC,UAAU,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,GAAG,OAAO;IAK3C,GAAG,CAAC,UAAU,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,GAAG,IAAI;IAKtD,GAAG,CAAC,UAAU,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,GAAG,KAAK,GAAG,SAAS;IACrD,GAAG,CAAC,UAAU,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,KAAK,GAAG,KAAK;IAehE,MAAM,CAAC,UAAU,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,GAAG,OAAO;IAK9C,KAAK,IAAI,IAAI;IACb,KAAK,CAAC,UAAU,EAAE,OAAO,GAAG,IAAI;IAWhC,SAAS,CAAC,eAAe,CAAC,UAAU,EAAE,OAAO,GAAG,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC;CASlE;AAED;;;GAGG;AACH,qBAAa,aAAa,CAAC,CAAC,EAAE,CAAC,CAAE,SAAQ,YAAY,CAAC,GAAG,GAAG,MAAM,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IAE7E;;;;;;;;;;;OAWG;gBACS,cAAc,EAAE,yBAAyB,EAAE,KAAK,CAAC,EAAE,aAAa;CAoB/E;AAED;;;GAGG;AACH,qBAAa,cAAc,CAAC,CAAC,EAAE,CAAC,CAAE,SAAQ,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC;IAEvD;;;;;;;OAOG;gBACS,cAAc,EAAE,yBAAyB,EAAE,KAAK,CAAC,EAAE,aAAa;CAiB/E"}
+186
View File
@@ -0,0 +1,186 @@
/******************************************************************************
* Copyright 2023 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.
******************************************************************************/
export class DisposableCache {
constructor() {
this.toDispose = [];
this.isDisposed = false;
}
onDispose(disposable) {
this.toDispose.push(disposable);
}
dispose() {
this.throwIfDisposed();
this.clear();
this.isDisposed = true;
this.toDispose.forEach(disposable => disposable.dispose());
}
throwIfDisposed() {
if (this.isDisposed) {
throw new Error('This cache has already been disposed');
}
}
}
export class SimpleCache extends DisposableCache {
constructor() {
super(...arguments);
this.cache = new Map();
}
has(key) {
this.throwIfDisposed();
return this.cache.has(key);
}
set(key, value) {
this.throwIfDisposed();
this.cache.set(key, value);
}
get(key, provider) {
this.throwIfDisposed();
if (this.cache.has(key)) {
return this.cache.get(key);
}
else if (provider) {
const value = provider();
this.cache.set(key, value);
return value;
}
else {
return undefined;
}
}
delete(key) {
this.throwIfDisposed();
return this.cache.delete(key);
}
clear() {
this.throwIfDisposed();
this.cache.clear();
}
}
export class ContextCache extends DisposableCache {
constructor(converter) {
super();
this.cache = new Map();
this.converter = converter ?? (value => value);
}
has(contextKey, key) {
this.throwIfDisposed();
return this.cacheForContext(contextKey).has(key);
}
set(contextKey, key, value) {
this.throwIfDisposed();
this.cacheForContext(contextKey).set(key, value);
}
get(contextKey, key, provider) {
this.throwIfDisposed();
const contextCache = this.cacheForContext(contextKey);
if (contextCache.has(key)) {
return contextCache.get(key);
}
else if (provider) {
const value = provider();
contextCache.set(key, value);
return value;
}
else {
return undefined;
}
}
delete(contextKey, key) {
this.throwIfDisposed();
return this.cacheForContext(contextKey).delete(key);
}
clear(contextKey) {
this.throwIfDisposed();
if (contextKey) {
const mapKey = this.converter(contextKey);
this.cache.delete(mapKey);
}
else {
this.cache.clear();
}
}
cacheForContext(contextKey) {
const mapKey = this.converter(contextKey);
let documentCache = this.cache.get(mapKey);
if (!documentCache) {
documentCache = new Map();
this.cache.set(mapKey, documentCache);
}
return documentCache;
}
}
/**
* Every key/value pair in this cache is scoped to a document.
* If this document is changed or deleted, all associated key/value pairs are deleted.
*/
export class DocumentCache extends ContextCache {
/**
* Creates a new document cache.
*
* @param sharedServices Service container instance to hook into document lifecycle events.
* @param state Optional document state on which the cache should evict.
* If not provided, the cache will evict on `DocumentBuilder#onUpdate`.
* *Deleted* documents are considered in both cases.
*
* Providing a state here will use `DocumentBuilder#onDocumentPhase` instead,
* which triggers on all documents that have been affected by this change, assuming that the
* state is `DocumentState.Linked` or a later state.
*/
constructor(sharedServices, state) {
super(uri => uri.toString());
if (state) {
this.toDispose.push(sharedServices.workspace.DocumentBuilder.onDocumentPhase(state, document => {
this.clear(document.uri.toString());
}));
this.toDispose.push(sharedServices.workspace.DocumentBuilder.onUpdate((_changed, deleted) => {
for (const uri of deleted) { // react only on deleted documents
this.clear(uri);
}
}));
}
else {
this.toDispose.push(sharedServices.workspace.DocumentBuilder.onUpdate((changed, deleted) => {
const allUris = changed.concat(deleted); // react on both changed and deleted documents
for (const uri of allUris) {
this.clear(uri);
}
}));
}
}
}
/**
* Every key/value pair in this cache is scoped to the whole workspace.
* If any document in the workspace is added, changed or deleted, the whole cache is evicted.
*/
export class WorkspaceCache extends SimpleCache {
/**
* Creates a new workspace cache.
*
* @param sharedServices Service container instance to hook into document lifecycle events.
* @param state Optional document state on which the cache should evict.
* If not provided, the cache will evict on `DocumentBuilder#onUpdate`.
* *Deleted* documents are considered in both cases.
*/
constructor(sharedServices, state) {
super();
if (state) {
this.toDispose.push(sharedServices.workspace.DocumentBuilder.onBuildPhase(state, () => {
this.clear();
}));
this.toDispose.push(sharedServices.workspace.DocumentBuilder.onUpdate((_changed, deleted) => {
if (deleted.length > 0) { // react only on deleted documents
this.clear();
}
}));
}
else {
this.toDispose.push(sharedServices.workspace.DocumentBuilder.onUpdate(() => {
this.clear();
}));
}
}
}
//# sourceMappingURL=caching.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"caching.js","sourceRoot":"","sources":["../../src/utils/caching.ts"],"names":[],"mappings":"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"}
+7
View File
@@ -0,0 +1,7 @@
/******************************************************************************
* Copyright 2024 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.
******************************************************************************/
export * from 'vscode-jsonrpc/lib/common/cancellation.js';
//# sourceMappingURL=cancellation.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"cancellation.d.ts","sourceRoot":"","sources":["../../src/utils/cancellation.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAGhF,cAAc,2CAA2C,CAAC"}
+8
View File
@@ -0,0 +1,8 @@
/******************************************************************************
* Copyright 2024 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.
******************************************************************************/
// eslint-disable-next-line no-restricted-imports
export * from 'vscode-jsonrpc/lib/common/cancellation.js';
//# sourceMappingURL=cancellation.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"cancellation.js","sourceRoot":"","sources":["../../src/utils/cancellation.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAEhF,iDAAiD;AACjD,cAAc,2CAA2C,CAAC"}
+95
View File
@@ -0,0 +1,95 @@
/******************************************************************************
* 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 { Stream } from './stream.js';
/**
* A multimap is a variation of a Map that has potentially multiple values for every key.
*/
export declare class MultiMap<K, V> {
private map;
constructor();
constructor(elements: Iterable<[K, V]>);
/**
* The total number of values in the multimap.
*/
get size(): number;
/**
* Clear all entries in the multimap.
*/
clear(): void;
/**
* Operates differently depending on whether a `value` is given:
* * With a value, this method deletes the specific key / value pair from the multimap.
* * Without a value, all values associated with the given key are deleted.
*
* @returns `true` if a value existed and has been removed, or `false` if the specified
* key / value does not exist.
*/
delete(key: K, value?: V): boolean;
/**
* Returns an array of all values associated with the given key. If no value exists,
* an empty array is returned.
*
* _Note:_ The returned array is assumed not to be modified. Use the `set` method to add a
* value and `delete` to remove a value from the multimap.
*/
get(key: K): readonly V[];
/**
* Returns a stream of all values associated with the given key. If no value exists,
* {@link EMPTY_STREAM} is returned.
*/
getStream(key: K): Stream<V>;
/**
* Operates differently depending on whether a `value` is given:
* * With a value, this method returns `true` if the specific key / value pair is present in the multimap.
* * Without a value, this method returns `true` if the given key is present in the multimap.
*/
has(key: K, value?: V): boolean;
/**
* Add the given key / value pair to the multimap.
*/
add(key: K, value: V): this;
/**
* Add the given set of key / value pairs to the multimap.
*/
addAll(key: K, values: Iterable<V>): this;
/**
* Invokes the given callback function for every key / value pair in the multimap.
*/
forEach(callbackfn: (value: V, key: K, map: this) => void): void;
/**
* Returns an iterator of key, value pairs for every entry in the map.
*/
[Symbol.iterator](): Iterator<[K, V]>;
/**
* Returns a stream of key, value pairs for every entry in the map.
*/
entries(): Stream<[K, V]>;
/**
* Returns a stream of keys in the map.
*/
keys(): Stream<K>;
/**
* Returns a stream of values in the map.
*/
values(): Stream<V>;
/**
* Returns a stream of key, value set pairs for every key in the map.
*/
entriesGroupedByKey(): Stream<[K, V[]]>;
}
export declare class BiMap<K, V> {
private map;
private inverse;
get size(): number;
constructor();
constructor(elements: Array<[K, V]>);
clear(): void;
set(key: K, value: V): this;
get(key: K): V | undefined;
getKey(value: V): K | undefined;
delete(key: K): boolean;
}
//# sourceMappingURL=collections.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"collections.d.ts","sourceRoot":"","sources":["../../src/utils/collections.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAEhF,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAG1C;;GAEG;AACH,qBAAa,QAAQ,CAAC,CAAC,EAAE,CAAC;IAEtB,OAAO,CAAC,GAAG,CAAqB;;gBAGpB,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAStC;;OAEG;IACH,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED;;OAEG;IACH,KAAK,IAAI,IAAI;IAIb;;;;;;;OAOG;IACH,MAAM,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,CAAC,EAAE,CAAC,GAAG,OAAO;IAoBlC;;;;;;OAMG;IACH,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,SAAS,CAAC,EAAE;IAIzB;;;OAGG;IACH,SAAS,CAAC,GAAG,EAAE,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC;IAK5B;;;;OAIG;IACH,GAAG,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,CAAC,EAAE,CAAC,GAAG,OAAO;IAY/B;;OAEG;IACH,GAAG,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI;IAS3B;;OAEG;IACH,MAAM,CAAC,GAAG,EAAE,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,IAAI;IASzC;;OAEG;IACH,OAAO,CAAC,UAAU,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,IAAI,KAAK,IAAI,GAAG,IAAI;IAMhE;;OAEG;IACH,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAIrC;;OAEG;IACH,OAAO,IAAI,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAKzB;;OAEG;IACH,IAAI,IAAI,MAAM,CAAC,CAAC,CAAC;IAIjB;;OAEG;IACH,MAAM,IAAI,MAAM,CAAC,CAAC,CAAC;IAInB;;OAEG;IACH,mBAAmB,IAAI,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;CAI1C;AAED,qBAAa,KAAK,CAAC,CAAC,EAAE,CAAC;IAEnB,OAAO,CAAC,GAAG,CAAmB;IAC9B,OAAO,CAAC,OAAO,CAAmB;IAElC,IAAI,IAAI,IAAI,MAAM,CAEjB;;gBAGW,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IASnC,KAAK,IAAI,IAAI;IAKb,GAAG,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI;IAM3B,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG,SAAS;IAI1B,MAAM,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,GAAG,SAAS;IAI/B,MAAM,CAAC,GAAG,EAAE,CAAC,GAAG,OAAO;CAS1B"}
+195
View File
@@ -0,0 +1,195 @@
/******************************************************************************
* 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 { EMPTY_STREAM, Reduction, stream } from './stream.js';
/**
* A multimap is a variation of a Map that has potentially multiple values for every key.
*/
export class MultiMap {
constructor(elements) {
this.map = new Map();
if (elements) {
for (const [key, value] of elements) {
this.add(key, value);
}
}
}
/**
* The total number of values in the multimap.
*/
get size() {
return Reduction.sum(stream(this.map.values()).map(a => a.length));
}
/**
* Clear all entries in the multimap.
*/
clear() {
this.map.clear();
}
/**
* Operates differently depending on whether a `value` is given:
* * With a value, this method deletes the specific key / value pair from the multimap.
* * Without a value, all values associated with the given key are deleted.
*
* @returns `true` if a value existed and has been removed, or `false` if the specified
* key / value does not exist.
*/
delete(key, value) {
if (value === undefined) {
return this.map.delete(key);
}
else {
const values = this.map.get(key);
if (values) {
const index = values.indexOf(value);
if (index >= 0) {
if (values.length === 1) {
this.map.delete(key);
}
else {
values.splice(index, 1);
}
return true;
}
}
return false;
}
}
/**
* Returns an array of all values associated with the given key. If no value exists,
* an empty array is returned.
*
* _Note:_ The returned array is assumed not to be modified. Use the `set` method to add a
* value and `delete` to remove a value from the multimap.
*/
get(key) {
return this.map.get(key) ?? [];
}
/**
* Returns a stream of all values associated with the given key. If no value exists,
* {@link EMPTY_STREAM} is returned.
*/
getStream(key) {
const values = this.map.get(key);
return values ? stream(values) : EMPTY_STREAM;
}
/**
* Operates differently depending on whether a `value` is given:
* * With a value, this method returns `true` if the specific key / value pair is present in the multimap.
* * Without a value, this method returns `true` if the given key is present in the multimap.
*/
has(key, value) {
if (value === undefined) {
return this.map.has(key);
}
else {
const values = this.map.get(key);
if (values) {
return values.indexOf(value) >= 0;
}
return false;
}
}
/**
* Add the given key / value pair to the multimap.
*/
add(key, value) {
if (this.map.has(key)) {
this.map.get(key).push(value);
}
else {
this.map.set(key, [value]);
}
return this;
}
/**
* Add the given set of key / value pairs to the multimap.
*/
addAll(key, values) {
if (this.map.has(key)) {
this.map.get(key).push(...values);
}
else {
this.map.set(key, Array.from(values));
}
return this;
}
/**
* Invokes the given callback function for every key / value pair in the multimap.
*/
forEach(callbackfn) {
this.map.forEach((array, key) => array.forEach(value => callbackfn(value, key, this)));
}
/**
* Returns an iterator of key, value pairs for every entry in the map.
*/
[Symbol.iterator]() {
return this.entries().iterator();
}
/**
* Returns a stream of key, value pairs for every entry in the map.
*/
entries() {
return stream(this.map.entries())
.flatMap(([key, array]) => array.map(value => [key, value]));
}
/**
* Returns a stream of keys in the map.
*/
keys() {
return stream(this.map.keys());
}
/**
* Returns a stream of values in the map.
*/
values() {
return stream(this.map.values()).flat();
}
/**
* Returns a stream of key, value set pairs for every key in the map.
*/
entriesGroupedByKey() {
return stream(this.map.entries());
}
}
export class BiMap {
get size() {
return this.map.size;
}
constructor(elements) {
this.map = new Map();
this.inverse = new Map();
if (elements) {
for (const [key, value] of elements) {
this.set(key, value);
}
}
}
clear() {
this.map.clear();
this.inverse.clear();
}
set(key, value) {
this.map.set(key, value);
this.inverse.set(value, key);
return this;
}
get(key) {
return this.map.get(key);
}
getKey(value) {
return this.inverse.get(value);
}
delete(key) {
const value = this.map.get(key);
if (value !== undefined) {
this.map.delete(key);
this.inverse.delete(value);
return true;
}
return false;
}
}
//# sourceMappingURL=collections.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"collections.js","sourceRoot":"","sources":["../../src/utils/collections.ts"],"names":[],"mappings":"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"}
+80
View File
@@ -0,0 +1,80 @@
/******************************************************************************
* 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 { IToken } from '@chevrotain/types';
import type { Range } from 'vscode-languageserver-types';
import type { CstNode, LeafCstNode } from '../syntax-tree.js';
import type { DocumentSegment } from '../workspace/documents.js';
import type { Stream, TreeStream } from './stream.js';
/**
* Attempts to find the CST node that belongs to the datatype element that contains the given CST node.
*
* @param cstNode The CST node for which to find the datatype node.
* @returns The CST node corresponding to the datatype element, or the undefined if no such element exists.
*/
export declare function getDatatypeNode(cstNode: CstNode): CstNode | undefined;
/**
* Create a stream of all CST nodes that are directly and indirectly contained in the given root node,
* including the root node itself.
*/
export declare function streamCst(node: CstNode): TreeStream<CstNode>;
/**
* Create a stream of all leaf nodes that are directly and indirectly contained in the given root node.
*/
export declare function flattenCst(node: CstNode): Stream<LeafCstNode>;
/**
* Determines whether the specified cst node is a child of the specified parent node.
*/
export declare function isChildNode(child: CstNode, parent: CstNode): boolean;
export declare function tokenToRange(token: IToken): Range;
export declare function toDocumentSegment(node: CstNode): DocumentSegment;
export declare function toDocumentSegment(node?: CstNode): DocumentSegment | undefined;
export declare enum RangeComparison {
Before = 0,
After = 1,
OverlapFront = 2,
OverlapBack = 3,
Inside = 4,
Outside = 5
}
export declare function compareRange(range: Range, to: Range): RangeComparison;
export declare function inRange(range: Range, to: Range): boolean;
export declare const DefaultNameRegexp: RegExp;
/**
* Performs `findLeafNodeAtOffset` with a minor difference: When encountering a character that matches the `nameRegexp` argument,
* it will instead return the leaf node at the `offset - 1` position.
*
* For LSP services, users expect that the declaration of an element is available if the cursor is directly after the element.
*/
export declare function findDeclarationNodeAtOffset(cstNode: CstNode | undefined, offset: number, nameRegexp?: RegExp): LeafCstNode | undefined;
export declare function findCommentNode(cstNode: CstNode | undefined, commentNames: string[]): CstNode | undefined;
export declare function isCommentNode(cstNode: CstNode, commentNames: string[]): boolean;
/**
* Finds the leaf CST node at the specified 0-based string offset.
* Note that the given offset will be within the range of the returned leaf node.
*
* If the offset does not point to a CST node (but just white space), this method will return `undefined`.
*
* @param node The CST node to search through.
* @param offset The specified offset.
* @returns The CST node at the specified offset.
*/
export declare function findLeafNodeAtOffset(node: CstNode, offset: number): LeafCstNode | undefined;
/**
* Finds the leaf CST node at the specified 0-based string offset.
* If no CST node exists at the specified position, it will return the leaf node before it.
*
* If there is no leaf node before the specified offset, this method will return `undefined`.
*
* @param node The CST node to search through.
* @param offset The specified offset.
* @returns The CST node closest to the specified offset.
*/
export declare function findLeafNodeBeforeOffset(node: CstNode, offset: number): LeafCstNode | undefined;
export declare function getPreviousNode(node: CstNode, hidden?: boolean): CstNode | undefined;
export declare function getNextNode(node: CstNode, hidden?: boolean): CstNode | undefined;
export declare function getStartlineNode(node: CstNode): CstNode;
export declare function getInteriorNodes(start: CstNode, end: CstNode): CstNode[];
//# sourceMappingURL=cst-utils.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"cst-utils.d.ts","sourceRoot":"","sources":["../../src/utils/cst-utils.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAEhF,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,6BAA6B,CAAC;AACzD,OAAO,KAAK,EAAE,OAAO,EAAoB,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAMtD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,GAAG,SAAS,CAkBrE;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,OAAO,GAAG,UAAU,CAAC,OAAO,CAAC,CAQ5D;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAAC,WAAW,CAAC,CAE7D;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAQpE;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,CAajD;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,OAAO,GAAG,eAAe,CAAC;AAClE,wBAAgB,iBAAiB,CAAC,IAAI,CAAC,EAAE,OAAO,GAAG,eAAe,GAAG,SAAS,CAAC;AAc/E,oBAAY,eAAe;IACvB,MAAM,IAAI;IACV,KAAK,IAAI;IACT,YAAY,IAAI;IAChB,WAAW,IAAI;IACf,MAAM,IAAI;IACV,OAAO,IAAI;CACd;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,GAAG,eAAe,CAiBrE;AAED,wBAAgB,OAAO,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,GAAG,OAAO,CAGxD;AAID,eAAO,MAAM,iBAAiB,QAAiB,CAAC;AAEhD;;;;;GAKG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,SAAoB,GAAG,WAAW,GAAG,SAAS,CAYjJ;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG,OAAO,GAAG,SAAS,CAmBzG;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG,OAAO,CAE/E;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CAU3F;AAED;;;;;;;;;GASG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CAU/F;AA6BD,wBAAgB,eAAe,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,UAAO,GAAG,OAAO,GAAG,SAAS,CAcjF;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,UAAO,GAAG,OAAO,GAAG,SAAS,CAe7E;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAuBvD;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,GAAG,OAAO,EAAE,CAMxE"}
+338
View File
@@ -0,0 +1,338 @@
/******************************************************************************
* 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 { isCompositeCstNode, isLeafCstNode, isRootCstNode } from '../syntax-tree.js';
import { TreeStreamImpl } from './stream.js';
import { getContainerOfType } from './ast-utils.js';
import { isParserRule } from '../languages/generated/ast.js';
/**
* Attempts to find the CST node that belongs to the datatype element that contains the given CST node.
*
* @param cstNode The CST node for which to find the datatype node.
* @returns The CST node corresponding to the datatype element, or the undefined if no such element exists.
*/
export function getDatatypeNode(cstNode) {
let current = cstNode;
let found = false;
while (current) {
const definingRule = getContainerOfType(current.grammarSource, isParserRule);
if (definingRule && definingRule.dataType) {
// Go up the chain. This element might be part of a larger datatype rule
current = current.container;
found = true;
}
else if (found) {
// The last datatype node is the one we are looking for
return current;
}
else {
// We haven't found any datatype node yet and we've reached a non-datatype rule
return undefined;
}
}
return undefined;
}
/**
* Create a stream of all CST nodes that are directly and indirectly contained in the given root node,
* including the root node itself.
*/
export function streamCst(node) {
return new TreeStreamImpl(node, element => {
if (isCompositeCstNode(element)) {
return element.content;
}
else {
return [];
}
}, { includeRoot: true });
}
/**
* Create a stream of all leaf nodes that are directly and indirectly contained in the given root node.
*/
export function flattenCst(node) {
return streamCst(node).filter(isLeafCstNode);
}
/**
* Determines whether the specified cst node is a child of the specified parent node.
*/
export function isChildNode(child, parent) {
while (child.container) {
child = child.container;
if (child === parent) {
return true;
}
}
return false;
}
export function tokenToRange(token) {
// Chevrotain uses 1-based indices everywhere
// So we subtract 1 from every value to align with the LSP
return {
start: {
character: token.startColumn - 1,
line: token.startLine - 1
},
end: {
character: token.endColumn, // endColumn uses the correct index
line: token.endLine - 1
}
};
}
export function toDocumentSegment(node) {
if (!node) {
return undefined;
}
const { offset, end, range } = node;
return {
range,
offset,
end,
length: end - offset
};
}
export var RangeComparison;
(function (RangeComparison) {
RangeComparison[RangeComparison["Before"] = 0] = "Before";
RangeComparison[RangeComparison["After"] = 1] = "After";
RangeComparison[RangeComparison["OverlapFront"] = 2] = "OverlapFront";
RangeComparison[RangeComparison["OverlapBack"] = 3] = "OverlapBack";
RangeComparison[RangeComparison["Inside"] = 4] = "Inside";
RangeComparison[RangeComparison["Outside"] = 5] = "Outside";
})(RangeComparison || (RangeComparison = {}));
export function compareRange(range, to) {
if (range.end.line < to.start.line || (range.end.line === to.start.line && range.end.character <= to.start.character)) {
return RangeComparison.Before;
}
else if (range.start.line > to.end.line || (range.start.line === to.end.line && range.start.character >= to.end.character)) {
return RangeComparison.After;
}
const startInside = range.start.line > to.start.line || (range.start.line === to.start.line && range.start.character >= to.start.character);
const endInside = range.end.line < to.end.line || (range.end.line === to.end.line && range.end.character <= to.end.character);
if (startInside && endInside) {
return RangeComparison.Inside;
}
else if (startInside) {
return RangeComparison.OverlapBack;
}
else if (endInside) {
return RangeComparison.OverlapFront;
}
else {
return RangeComparison.Outside;
}
}
export function inRange(range, to) {
const comparison = compareRange(range, to);
return comparison > RangeComparison.After;
}
// The \p{L} regex matches any unicode letter character, i.e. characters from non-english alphabets
// Together with \w it matches any kind of character which can commonly appear in IDs
export const DefaultNameRegexp = /^[\w\p{L}]$/u;
/**
* Performs `findLeafNodeAtOffset` with a minor difference: When encountering a character that matches the `nameRegexp` argument,
* it will instead return the leaf node at the `offset - 1` position.
*
* For LSP services, users expect that the declaration of an element is available if the cursor is directly after the element.
*/
export function findDeclarationNodeAtOffset(cstNode, offset, nameRegexp = DefaultNameRegexp) {
if (cstNode) {
if (offset > 0) {
const localOffset = offset - cstNode.offset;
const textAtOffset = cstNode.text.charAt(localOffset);
if (!nameRegexp.test(textAtOffset)) {
offset--;
}
}
return findLeafNodeAtOffset(cstNode, offset);
}
return undefined;
}
export function findCommentNode(cstNode, commentNames) {
if (cstNode) {
const previous = getPreviousNode(cstNode, true);
if (previous && isCommentNode(previous, commentNames)) {
return previous;
}
if (isRootCstNode(cstNode)) {
// Go from the first non-hidden node through all nodes in reverse order
// We do this to find the comment node which directly precedes the root node
const endIndex = cstNode.content.findIndex(e => !e.hidden);
for (let i = endIndex - 1; i >= 0; i--) {
const child = cstNode.content[i];
if (isCommentNode(child, commentNames)) {
return child;
}
}
}
}
return undefined;
}
export function isCommentNode(cstNode, commentNames) {
return isLeafCstNode(cstNode) && commentNames.includes(cstNode.tokenType.name);
}
/**
* Finds the leaf CST node at the specified 0-based string offset.
* Note that the given offset will be within the range of the returned leaf node.
*
* If the offset does not point to a CST node (but just white space), this method will return `undefined`.
*
* @param node The CST node to search through.
* @param offset The specified offset.
* @returns The CST node at the specified offset.
*/
export function findLeafNodeAtOffset(node, offset) {
if (isLeafCstNode(node)) {
return node;
}
else if (isCompositeCstNode(node)) {
const searchResult = binarySearch(node, offset, false);
if (searchResult) {
return findLeafNodeAtOffset(searchResult, offset);
}
}
return undefined;
}
/**
* Finds the leaf CST node at the specified 0-based string offset.
* If no CST node exists at the specified position, it will return the leaf node before it.
*
* If there is no leaf node before the specified offset, this method will return `undefined`.
*
* @param node The CST node to search through.
* @param offset The specified offset.
* @returns The CST node closest to the specified offset.
*/
export function findLeafNodeBeforeOffset(node, offset) {
if (isLeafCstNode(node)) {
return node;
}
else if (isCompositeCstNode(node)) {
const searchResult = binarySearch(node, offset, true);
if (searchResult) {
return findLeafNodeBeforeOffset(searchResult, offset);
}
}
return undefined;
}
function binarySearch(node, offset, closest) {
let left = 0;
let right = node.content.length - 1;
let closestNode = undefined;
while (left <= right) {
const middle = Math.floor((left + right) / 2);
const middleNode = node.content[middle];
if (middleNode.offset <= offset && middleNode.end > offset) {
// Found an exact match
return middleNode;
}
if (middleNode.end <= offset) {
// Update the closest node (less than offset) and move to the right half
closestNode = closest ? middleNode : undefined;
left = middle + 1;
}
else {
// Move to the left half
right = middle - 1;
}
}
return closestNode;
}
export function getPreviousNode(node, hidden = true) {
while (node.container) {
const parent = node.container;
let index = parent.content.indexOf(node);
while (index > 0) {
index--;
const previous = parent.content[index];
if (hidden || !previous.hidden) {
return previous;
}
}
node = parent;
}
return undefined;
}
export function getNextNode(node, hidden = true) {
while (node.container) {
const parent = node.container;
let index = parent.content.indexOf(node);
const last = parent.content.length - 1;
while (index < last) {
index++;
const next = parent.content[index];
if (hidden || !next.hidden) {
return next;
}
}
node = parent;
}
return undefined;
}
export function getStartlineNode(node) {
if (node.range.start.character === 0) {
return node;
}
const line = node.range.start.line;
let last = node;
let index;
while (node.container) {
const parent = node.container;
const selfIndex = index ?? parent.content.indexOf(node);
if (selfIndex === 0) {
node = parent;
index = undefined;
}
else {
index = selfIndex - 1;
node = parent.content[index];
}
if (node.range.start.line !== line) {
break;
}
last = node;
}
return last;
}
export function getInteriorNodes(start, end) {
const commonParent = getCommonParent(start, end);
if (!commonParent) {
return [];
}
return commonParent.parent.content.slice(commonParent.a + 1, commonParent.b);
}
function getCommonParent(a, b) {
const aParents = getParentChain(a);
const bParents = getParentChain(b);
let current;
for (let i = 0; i < aParents.length && i < bParents.length; i++) {
const aParent = aParents[i];
const bParent = bParents[i];
if (aParent.parent === bParent.parent) {
current = {
parent: aParent.parent,
a: aParent.index,
b: bParent.index
};
}
else {
break;
}
}
return current;
}
function getParentChain(node) {
const chain = [];
while (node.container) {
const parent = node.container;
const index = parent.content.indexOf(node);
chain.push({
parent,
index
});
node = parent;
}
return chain.reverse();
}
//# sourceMappingURL=cst-utils.js.map
File diff suppressed because one or more lines are too long
+22
View File
@@ -0,0 +1,22 @@
/******************************************************************************
* 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.
******************************************************************************/
export interface Disposable {
/**
* Dispose this object.
*/
dispose(): void;
}
export interface AsyncDisposable {
/**
* Dispose this object.
*/
dispose(): Promise<void>;
}
export declare namespace Disposable {
function create(callback: () => Promise<void>): AsyncDisposable;
function create(callback: () => void): Disposable;
}
//# sourceMappingURL=disposable.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"disposable.d.ts","sourceRoot":"","sources":["../../src/utils/disposable.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAEhF,MAAM,WAAW,UAAU;IACvB;;OAEG;IACH,OAAO,IAAI,IAAI,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC5B;;OAEG;IACH,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B;AAED,yBAAiB,UAAU,CAAC;IACxB,SAAgB,MAAM,CAAC,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,eAAe,CAAC;IACvE,SAAgB,MAAM,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,UAAU,CAAC;CAM5D"}
+15
View File
@@ -0,0 +1,15 @@
/******************************************************************************
* 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.
******************************************************************************/
export var Disposable;
(function (Disposable) {
function create(callback) {
return {
dispose: async () => await callback()
};
}
Disposable.create = create;
})(Disposable || (Disposable = {}));
//# sourceMappingURL=disposable.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"disposable.js","sourceRoot":"","sources":["../../src/utils/disposable.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAgBhF,MAAM,KAAW,UAAU,CAQ1B;AARD,WAAiB,UAAU;IAGvB,SAAgB,MAAM,CAAC,QAAoC;QACvD,OAAO;YACH,OAAO,EAAE,KAAK,IAAI,EAAE,CAAC,MAAM,QAAQ,EAAE;SACxC,CAAC;IACN,CAAC;IAJe,iBAAM,SAIrB,CAAA;AACL,CAAC,EARgB,UAAU,KAAV,UAAU,QAQ1B"}
+12
View File
@@ -0,0 +1,12 @@
/******************************************************************************
* 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 { CstNode } from '../syntax-tree.js';
export declare class ErrorWithLocation extends Error {
constructor(node: CstNode | undefined, message: string);
}
export declare function assertUnreachable(_: never, message?: string): never;
export declare function assertCondition(condition: boolean, message?: string): asserts condition;
//# sourceMappingURL=errors.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/utils/errors.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAEhF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAEjD,qBAAa,iBAAkB,SAAQ,KAAK;gBAC5B,IAAI,EAAE,OAAO,GAAG,SAAS,EAAE,OAAO,EAAE,MAAM;CAGzD;AAED,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,SAAiC,GAAG,KAAK,CAE3F;AAED,wBAAgB,eAAe,CAAC,SAAS,EAAE,OAAO,EAAE,OAAO,GAAE,MAAwC,GAAG,OAAO,CAAC,SAAS,CAIxH"}
+19
View File
@@ -0,0 +1,19 @@
/******************************************************************************
* 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.
******************************************************************************/
export class ErrorWithLocation extends Error {
constructor(node, message) {
super(node ? `${message} at ${node.range.start.line}:${node.range.start.character}` : message);
}
}
export function assertUnreachable(_, message = 'Error: Got unexpected value.') {
throw new Error(message);
}
export function assertCondition(condition, message = 'Error: Condition is violated.') {
if (!condition) {
throw new Error(message);
}
}
//# sourceMappingURL=errors.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/utils/errors.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAIhF,MAAM,OAAO,iBAAkB,SAAQ,KAAK;IACxC,YAAY,IAAyB,EAAE,OAAe;QAClD,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,OAAO,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;IACnG,CAAC;CACJ;AAED,MAAM,UAAU,iBAAiB,CAAC,CAAQ,EAAE,OAAO,GAAG,8BAA8B;IAChF,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC;AAC7B,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,SAAkB,EAAE,UAAkB,+BAA+B;IACjG,IAAI,CAAC,SAAS,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC;IAC7B,CAAC;AACL,CAAC"}
+7
View File
@@ -0,0 +1,7 @@
/******************************************************************************
* Copyright 2024 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.
******************************************************************************/
export * from 'vscode-jsonrpc/lib/common/events.js';
//# sourceMappingURL=event.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"event.d.ts","sourceRoot":"","sources":["../../src/utils/event.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAGhF,cAAc,qCAAqC,CAAC"}
+8
View File
@@ -0,0 +1,8 @@
/******************************************************************************
* Copyright 2024 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.
******************************************************************************/
// eslint-disable-next-line no-restricted-imports
export * from 'vscode-jsonrpc/lib/common/events.js';
//# sourceMappingURL=event.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"event.js","sourceRoot":"","sources":["../../src/utils/event.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAEhF,iDAAiD;AACjD,cAAc,qCAAqC,CAAC"}
+12
View File
@@ -0,0 +1,12 @@
/******************************************************************************
* Copyright 2023 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 ast from '../languages/generated/ast.js';
/**
* Load a Langium grammar for your language from a JSON string. This is used by several services,
* most notably the parser builder which interprets the grammar to create a parser.
*/
export declare function loadGrammarFromJson(json: string): ast.Grammar;
//# sourceMappingURL=grammar-loader.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"grammar-loader.d.ts","sourceRoot":"","sources":["../../src/utils/grammar-loader.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAKhF,OAAO,KAAK,GAAG,MAAM,+BAA+B,CAAC;AAgCrD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,CAAC,OAAO,CAK7D"}
+38
View File
@@ -0,0 +1,38 @@
/******************************************************************************
* Copyright 2023 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 { createDefaultCoreModule, createDefaultSharedCoreModule } from '../default-module.js';
import { inject } from '../dependency-injection.js';
import * as ast from '../languages/generated/ast.js';
import { EmptyFileSystem } from '../workspace/file-system-provider.js';
import { URI } from './uri-utils.js';
const minimalGrammarModule = {
Grammar: () => undefined,
LanguageMetaData: () => ({
caseInsensitive: false,
fileExtensions: ['.langium'],
languageId: 'langium'
})
};
const minimalSharedGrammarModule = {
AstReflection: () => new ast.LangiumGrammarAstReflection()
};
function createMinimalGrammarServices() {
const shared = inject(createDefaultSharedCoreModule(EmptyFileSystem), minimalSharedGrammarModule);
const grammar = inject(createDefaultCoreModule({ shared }), minimalGrammarModule);
shared.ServiceRegistry.register(grammar);
return grammar;
}
/**
* Load a Langium grammar for your language from a JSON string. This is used by several services,
* most notably the parser builder which interprets the grammar to create a parser.
*/
export function loadGrammarFromJson(json) {
const services = createMinimalGrammarServices();
const astNode = services.serializer.JsonSerializer.deserialize(json);
services.shared.workspace.LangiumDocumentFactory.fromModel(astNode, URI.parse(`memory:/${astNode.name ?? 'grammar'}.langium`));
return astNode;
}
//# sourceMappingURL=grammar-loader.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"grammar-loader.js","sourceRoot":"","sources":["../../src/utils/grammar-loader.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAEhF,OAAO,EAAE,uBAAuB,EAAE,6BAA6B,EAAE,MAAM,sBAAsB,CAAC;AAE9F,OAAO,EAAE,MAAM,EAAE,MAAM,4BAA4B,CAAC;AACpD,OAAO,KAAK,GAAG,MAAM,+BAA+B,CAAC;AAGrD,OAAO,EAAE,eAAe,EAAE,MAAM,sCAAsC,CAAC;AACvE,OAAO,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAC;AAErC,MAAM,oBAAoB,GAA4D;IAClF,OAAO,EAAE,GAAG,EAAE,CAAC,SAAmC;IAClD,gBAAgB,EAAE,GAAG,EAAE,CAAC,CAAC;QACrB,eAAe,EAAE,KAAK;QACtB,cAAc,EAAE,CAAC,UAAU,CAAC;QAC5B,UAAU,EAAE,SAAS;KACxB,CAAC;CACL,CAAC;AAEF,MAAM,0BAA0B,GAAwE;IACpG,aAAa,EAAE,GAAG,EAAE,CAAC,IAAI,GAAG,CAAC,2BAA2B,EAAE;CAC7D,CAAC;AAEF,SAAS,4BAA4B;IACjC,MAAM,MAAM,GAAG,MAAM,CACjB,6BAA6B,CAAC,eAAe,CAAC,EAC9C,0BAA0B,CAC7B,CAAC;IACF,MAAM,OAAO,GAAG,MAAM,CAClB,uBAAuB,CAAC,EAAE,MAAM,EAAE,CAAC,EACnC,oBAAoB,CACvB,CAAC;IACF,MAAM,CAAC,eAAe,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IACzC,OAAO,OAAO,CAAC;AACnB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC5C,MAAM,QAAQ,GAAG,4BAA4B,EAAE,CAAC;IAChD,MAAM,OAAO,GAAG,QAAQ,CAAC,UAAU,CAAC,cAAc,CAAC,WAAW,CAAC,IAAI,CAAyB,CAAC;IAC7F,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC,sBAAsB,CAAC,SAAS,CAAC,OAAO,EAAE,GAAG,CAAC,KAAK,CAAC,WAAW,OAAO,CAAC,IAAI,IAAI,SAAS,UAAU,CAAC,CAAC,CAAC;IAC/H,OAAO,OAAO,CAAC;AACnB,CAAC"}
+127
View File
@@ -0,0 +1,127 @@
/******************************************************************************
* Copyright 2021-2022 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 ast from '../languages/generated/ast.js';
import type { AstNode, CstNode } from '../syntax-tree.js';
/**
* Returns the entry rule of the given grammar, if any. If the grammar file does not contain an entry rule,
* the result is `undefined`.
*/
export declare function getEntryRule(grammar: ast.Grammar): ast.ParserRule | undefined;
/**
* Returns all hidden terminal rules of the given grammar, if any.
*/
export declare function getHiddenRules(grammar: ast.Grammar): ast.AbstractRule[];
/**
* Returns all rules that can be reached from the topmost rules of the specified grammar (entry and hidden terminal rules).
*
* @param grammar The grammar that contains all rules
* @param allTerminals Whether or not to include terminals that are referenced only by other terminals
* @returns A list of referenced parser and terminal rules. If the grammar contains no entry rule,
* this function returns all rules of the specified grammar.
*/
export declare function getAllReachableRules(grammar: ast.Grammar, allTerminals: boolean): Set<ast.AbstractRule>;
/**
* Returns all parser rules which provide types which are used in the grammar as type in cross-references.
* @param grammar the grammar to investigate
* @returns the set of parser rules whose contributed types are used as type in cross-references
*/
export declare function getAllRulesUsedForCrossReferences(grammar: ast.Grammar): Set<ast.ParserRule>;
/**
* Determines the grammar expression used to parse a cross-reference (usually a reference to a terminal rule).
* A cross-reference can declare this expression explicitly in the form `[Type : Terminal]`, but if `Terminal`
* is omitted, this function attempts to infer it from the name of the referenced `Type` (using `findNameAssignment`).
*
* Returns the grammar expression used to parse the given cross-reference, or `undefined` if it is not declared
* and cannot be inferred.
*/
export declare function getCrossReferenceTerminal(crossRef: ast.CrossReference): ast.AbstractElement | undefined;
/**
* Determines whether the given terminal rule represents a comment. This is true if the rule is marked
* as `hidden` and it does not match white space. This means every hidden token (i.e. excluded from the AST)
* that contains visible characters is considered a comment.
*/
export declare function isCommentTerminal(terminalRule: ast.TerminalRule): boolean;
/**
* Find all CST nodes within the given node that contribute to the specified property.
*
* @param node A CST node in which to look for property assignments. If this is undefined, the result is an empty array.
* @param property A property name of the constructed AST node. If this is undefined, the result is an empty array.
*/
export declare function findNodesForProperty(node: CstNode | undefined, property: string | undefined): CstNode[];
/**
* Find a single CST node within the given node that contributes to the specified property.
*
* @param node A CST node in which to look for property assignments. If this is undefined, the result is `undefined`.
* @param property A property name of the constructed AST node. If this is undefined, the result is `undefined`.
* @param index If no index is specified or the index is less than zero, the first found node is returned. If the
* specified index exceeds the number of assignments to the property, the last found node is returned. Otherwise,
* the node with the specified index is returned.
*/
export declare function findNodeForProperty(node: CstNode | undefined, property: string | undefined, index?: number): CstNode | undefined;
/**
* Find all CST nodes within the given node that correspond to the specified keyword.
*
* @param node A CST node in which to look for keywords. If this is undefined, the result is an empty array.
* @param keyword A keyword as specified in the grammar.
*/
export declare function findNodesForKeyword(node: CstNode | undefined, keyword: string): CstNode[];
/**
* Find a single CST node within the given node that corresponds to the specified keyword.
*
* @param node A CST node in which to look for keywords. If this is undefined, the result is `undefined`.
* @param keyword A keyword as specified in the grammar.
* @param index If no index is specified or the index is less than zero, the first found node is returned. If the
* specified index exceeds the number of keyword occurrences, the last found node is returned. Otherwise,
* the node with the specified index is returned.
*/
export declare function findNodeForKeyword(node: CstNode | undefined, keyword: string, index?: number): CstNode | undefined;
export declare function findNodesForKeywordInternal(node: CstNode, keyword: string, element: AstNode | undefined): CstNode[];
/**
* If the given CST node was parsed in the context of a property assignment, the respective `Assignment` grammar
* node is returned. If no assignment is found, the result is `undefined`.
*
* @param cstNode A CST node for which to find a property assignment.
*/
export declare function findAssignment(cstNode: CstNode): ast.Assignment | undefined;
/**
* Find an assignment to the `name` property for the given grammar type. This requires the `type` to be inferred
* from a parser rule, and that rule must contain an assignment to the `name` property. In all other cases,
* this function returns `undefined`.
*/
export declare function findNameAssignment(type: ast.AbstractType): ast.Assignment | undefined;
export declare function getActionAtElement(element: ast.AbstractElement): ast.Action | undefined;
export type Cardinality = '?' | '*' | '+' | undefined;
export type Operator = '=' | '+=' | '?=' | undefined;
export declare function isOptionalCardinality(cardinality?: Cardinality, element?: ast.AbstractElement): boolean;
export declare function isArrayCardinality(cardinality?: Cardinality): boolean;
export declare function isArrayOperator(operator?: Operator): boolean;
/**
* Determines whether the given parser rule is a _data type rule_, meaning that it has a
* primitive return type like `number`, `boolean`, etc.
*/
export declare function isDataTypeRule(rule: ast.ParserRule): boolean;
export declare function isDataType(type: ast.Type): boolean;
export declare function getExplicitRuleType(rule: ast.AbstractRule): string | undefined;
export declare function getTypeName(type: ast.AbstractType | ast.Action): string;
export declare function getActionType(action: ast.Action): string | undefined;
/**
* This function is used at development time (for code generation and the internal type system) to get the type of the AST node produced by the given rule.
* For data type rules, the name of the rule is returned,
* e.g. "INT_value returns number: MY_INT;" returns "INT_value".
* @param rule the given rule
* @returns the name of the AST node type of the rule
*/
export declare function getRuleTypeName(rule: ast.AbstractRule): string;
/**
* This function is used at runtime to get the actual type of the values produced by the given rule at runtime.
* For data type rules, the name of the declared return type of the rule is returned (if any),
* e.g. "INT_value returns number: MY_INT;" returns "number".
* @param rule the given rule
* @returns the name of the type of the produced values of the rule at runtime
*/
export declare function getRuleType(rule: ast.AbstractRule): string;
export declare function terminalRegex(terminalRule: ast.TerminalRule): RegExp;
//# sourceMappingURL=grammar-utils.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"grammar-utils.d.ts","sourceRoot":"","sources":["../../src/utils/grammar-utils.ts"],"names":[],"mappings":"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"}
+606
View File
@@ -0,0 +1,606 @@
/******************************************************************************
* Copyright 2021-2022 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 { assertUnreachable } from '../utils/errors.js';
import * as ast from '../languages/generated/ast.js';
import { isCompositeCstNode } from '../syntax-tree.js';
import { getContainerOfType, streamAllContents } from './ast-utils.js';
import { streamCst } from './cst-utils.js';
import { escapeRegExp, isWhitespace } from './regexp-utils.js';
/**
* Returns the entry rule of the given grammar, if any. If the grammar file does not contain an entry rule,
* the result is `undefined`.
*/
export function getEntryRule(grammar) {
return grammar.rules.find(e => ast.isParserRule(e) && e.entry);
}
/**
* Returns all hidden terminal rules of the given grammar, if any.
*/
export function getHiddenRules(grammar) {
return grammar.rules.filter(e => ast.isTerminalRule(e) && e.hidden);
}
/**
* Returns all rules that can be reached from the topmost rules of the specified grammar (entry and hidden terminal rules).
*
* @param grammar The grammar that contains all rules
* @param allTerminals Whether or not to include terminals that are referenced only by other terminals
* @returns A list of referenced parser and terminal rules. If the grammar contains no entry rule,
* this function returns all rules of the specified grammar.
*/
export function getAllReachableRules(grammar, allTerminals) {
const ruleNames = new Set();
const entryRule = getEntryRule(grammar);
if (!entryRule) {
return new Set(grammar.rules);
}
const topMostRules = [entryRule].concat(getHiddenRules(grammar));
for (const rule of topMostRules) {
ruleDfs(rule, ruleNames, allTerminals);
}
const rules = new Set();
for (const rule of grammar.rules) {
if (ruleNames.has(rule.name) || (ast.isTerminalRule(rule) && rule.hidden)) {
rules.add(rule);
}
}
return rules;
}
function ruleDfs(rule, visitedSet, allTerminals) {
visitedSet.add(rule.name);
streamAllContents(rule).forEach(node => {
if (ast.isRuleCall(node) || (allTerminals && ast.isTerminalRuleCall(node))) {
const refRule = node.rule.ref;
if (refRule && !visitedSet.has(refRule.name)) {
ruleDfs(refRule, visitedSet, allTerminals);
}
}
});
}
/**
* Returns all parser rules which provide types which are used in the grammar as type in cross-references.
* @param grammar the grammar to investigate
* @returns the set of parser rules whose contributed types are used as type in cross-references
*/
export function getAllRulesUsedForCrossReferences(grammar) {
const result = new Set();
streamAllContents(grammar).forEach(node => {
if (ast.isCrossReference(node)) {
// the cross-reference refers directly to a parser rule (without "returns", without "infers")
if (ast.isParserRule(node.type.ref)) {
result.add(node.type.ref);
}
// the cross-reference refers to the explicitly inferred type of a parser rule
if (ast.isInferredType(node.type.ref) && ast.isParserRule(node.type.ref.$container)) {
result.add(node.type.ref.$container);
}
}
});
return result;
}
/**
* Determines the grammar expression used to parse a cross-reference (usually a reference to a terminal rule).
* A cross-reference can declare this expression explicitly in the form `[Type : Terminal]`, but if `Terminal`
* is omitted, this function attempts to infer it from the name of the referenced `Type` (using `findNameAssignment`).
*
* Returns the grammar expression used to parse the given cross-reference, or `undefined` if it is not declared
* and cannot be inferred.
*/
export function getCrossReferenceTerminal(crossRef) {
if (crossRef.terminal) {
return crossRef.terminal;
}
else if (crossRef.type.ref) {
const nameAssigment = findNameAssignment(crossRef.type.ref);
return nameAssigment?.terminal;
}
return undefined;
}
/**
* Determines whether the given terminal rule represents a comment. This is true if the rule is marked
* as `hidden` and it does not match white space. This means every hidden token (i.e. excluded from the AST)
* that contains visible characters is considered a comment.
*/
export function isCommentTerminal(terminalRule) {
return terminalRule.hidden && !isWhitespace(terminalRegex(terminalRule));
}
/**
* Find all CST nodes within the given node that contribute to the specified property.
*
* @param node A CST node in which to look for property assignments. If this is undefined, the result is an empty array.
* @param property A property name of the constructed AST node. If this is undefined, the result is an empty array.
*/
export function findNodesForProperty(node, property) {
if (!node || !property) {
return [];
}
return findNodesForPropertyInternal(node, property, node.astNode, true);
}
/**
* Find a single CST node within the given node that contributes to the specified property.
*
* @param node A CST node in which to look for property assignments. If this is undefined, the result is `undefined`.
* @param property A property name of the constructed AST node. If this is undefined, the result is `undefined`.
* @param index If no index is specified or the index is less than zero, the first found node is returned. If the
* specified index exceeds the number of assignments to the property, the last found node is returned. Otherwise,
* the node with the specified index is returned.
*/
export function findNodeForProperty(node, property, index) {
if (!node || !property) {
return undefined;
}
const nodes = findNodesForPropertyInternal(node, property, node.astNode, true);
if (nodes.length === 0) {
return undefined;
}
if (index !== undefined) {
index = Math.max(0, Math.min(index, nodes.length - 1));
}
else {
index = 0;
}
return nodes[index];
}
function findNodesForPropertyInternal(node, property, element, first) {
if (!first) {
const nodeFeature = getContainerOfType(node.grammarSource, ast.isAssignment);
if (nodeFeature && nodeFeature.feature === property) {
return [node];
}
}
if (isCompositeCstNode(node) && node.astNode === element) {
return node.content.flatMap(e => findNodesForPropertyInternal(e, property, element, false));
}
return [];
}
/**
* Find all CST nodes within the given node that correspond to the specified keyword.
*
* @param node A CST node in which to look for keywords. If this is undefined, the result is an empty array.
* @param keyword A keyword as specified in the grammar.
*/
export function findNodesForKeyword(node, keyword) {
if (!node) {
return [];
}
return findNodesForKeywordInternal(node, keyword, node?.astNode);
}
/**
* Find a single CST node within the given node that corresponds to the specified keyword.
*
* @param node A CST node in which to look for keywords. If this is undefined, the result is `undefined`.
* @param keyword A keyword as specified in the grammar.
* @param index If no index is specified or the index is less than zero, the first found node is returned. If the
* specified index exceeds the number of keyword occurrences, the last found node is returned. Otherwise,
* the node with the specified index is returned.
*/
export function findNodeForKeyword(node, keyword, index) {
if (!node) {
return undefined;
}
const nodes = findNodesForKeywordInternal(node, keyword, node?.astNode);
if (nodes.length === 0) {
return undefined;
}
if (index !== undefined) {
index = Math.max(0, Math.min(index, nodes.length - 1));
}
else {
index = 0;
}
return nodes[index];
}
export function findNodesForKeywordInternal(node, keyword, element) {
if (node.astNode !== element) {
return [];
}
if (ast.isKeyword(node.grammarSource) && node.grammarSource.value === keyword) {
return [node];
}
const treeIterator = streamCst(node).iterator();
let result;
const keywordNodes = [];
do {
result = treeIterator.next();
if (!result.done) {
const childNode = result.value;
if (childNode.astNode === element) {
if (ast.isKeyword(childNode.grammarSource) && childNode.grammarSource.value === keyword) {
keywordNodes.push(childNode);
}
}
else {
treeIterator.prune();
}
}
} while (!result.done);
return keywordNodes;
}
/**
* If the given CST node was parsed in the context of a property assignment, the respective `Assignment` grammar
* node is returned. If no assignment is found, the result is `undefined`.
*
* @param cstNode A CST node for which to find a property assignment.
*/
export function findAssignment(cstNode) {
const astNode = cstNode.astNode;
// Only search until the ast node of the parent cst node is no longer the original ast node
// This would make us jump to a preceding rule call, which contains only unrelated assignments
while (astNode === cstNode.container?.astNode) {
const assignment = getContainerOfType(cstNode.grammarSource, ast.isAssignment);
if (assignment) {
return assignment;
}
cstNode = cstNode.container;
}
return undefined;
}
/**
* Find an assignment to the `name` property for the given grammar type. This requires the `type` to be inferred
* from a parser rule, and that rule must contain an assignment to the `name` property. In all other cases,
* this function returns `undefined`.
*/
export function findNameAssignment(type) {
let startNode = type;
if (ast.isInferredType(startNode)) {
// for inferred types, the location to start searching for the name-assignment is different
if (ast.isAction(startNode.$container)) {
// a type which is explicitly inferred by an action: investigate the sibling of the Action node, i.e. start searching at the Action's parent
startNode = startNode.$container.$container;
}
else if (ast.isAbstractParserRule(startNode.$container)) {
// investigate the parser rule with the explicitly inferred type
startNode = startNode.$container;
}
else {
assertUnreachable(startNode.$container);
}
}
return findNameAssignmentInternal(type, startNode, new Map());
}
function findNameAssignmentInternal(type, startNode, cache) {
// the cache is only required to prevent infinite loops
function go(node, refType) {
let childAssignment = undefined;
const parentAssignment = getContainerOfType(node, ast.isAssignment);
// No parent assignment implies unassigned rule call
if (!parentAssignment) {
childAssignment = findNameAssignmentInternal(refType, refType, cache);
}
cache.set(type, childAssignment);
return childAssignment;
}
if (cache.has(type)) {
return cache.get(type);
}
cache.set(type, undefined);
for (const node of streamAllContents(startNode)) {
if (ast.isAssignment(node) && node.feature.toLowerCase() === 'name') {
cache.set(type, node);
return node;
}
else if (ast.isRuleCall(node) && ast.isParserRule(node.rule.ref)) {
return go(node, node.rule.ref);
}
else if (ast.isSimpleType(node) && node.typeRef?.ref) {
return go(node, node.typeRef.ref);
}
}
return undefined;
}
export function getActionAtElement(element) {
const parent = element.$container;
if (ast.isGroup(parent)) {
const elements = parent.elements;
const index = elements.indexOf(element);
for (let i = index - 1; i >= 0; i--) {
const item = elements[i];
if (ast.isAction(item)) {
return item;
}
else {
const action = streamAllContents(elements[i]).find(ast.isAction);
if (action) {
return action;
}
}
}
}
if (ast.isAbstractElement(parent)) {
return getActionAtElement(parent);
}
else {
return undefined;
}
}
export function isOptionalCardinality(cardinality, element) {
return cardinality === '?' || cardinality === '*' || (ast.isGroup(element) && Boolean(element.guardCondition));
}
export function isArrayCardinality(cardinality) {
return cardinality === '*' || cardinality === '+';
}
export function isArrayOperator(operator) {
return operator === '+=';
}
/**
* Determines whether the given parser rule is a _data type rule_, meaning that it has a
* primitive return type like `number`, `boolean`, etc.
*/
export function isDataTypeRule(rule) {
return isDataTypeRuleInternal(rule, new Set());
}
function isDataTypeRuleInternal(rule, visited) {
if (visited.has(rule)) {
return true;
}
else {
visited.add(rule);
}
for (const node of streamAllContents(rule)) {
if (ast.isRuleCall(node)) {
if (!node.rule.ref) {
// RuleCall to unresolved rule. Don't assume `rule` is a DataType rule.
return false;
}
if (ast.isParserRule(node.rule.ref) && !isDataTypeRuleInternal(node.rule.ref, visited)) {
return false;
}
if (ast.isInfixRule(node.rule.ref)) {
return false;
}
}
else if (ast.isAssignment(node)) {
return false;
}
else if (ast.isAction(node)) {
return false;
}
}
return Boolean(rule.definition);
}
export function isDataType(type) {
return isDataTypeInternal(type.type, new Set());
}
function isDataTypeInternal(type, visited) {
if (visited.has(type)) {
return true;
}
else {
visited.add(type);
}
if (ast.isArrayType(type)) {
return false;
}
else if (ast.isReferenceType(type)) {
return false;
}
else if (ast.isUnionType(type)) {
return type.types.every(e => isDataTypeInternal(e, visited));
}
else if (ast.isSimpleType(type)) {
if (type.primitiveType !== undefined) {
return true;
}
else if (type.stringType !== undefined) {
return true;
}
else if (type.typeRef !== undefined) {
const ref = type.typeRef.ref;
if (ast.isType(ref)) {
return isDataTypeInternal(ref.type, visited);
}
else {
return false;
}
}
else {
return false;
}
}
else {
return false;
}
}
export function getExplicitRuleType(rule) {
if (ast.isTerminalRule(rule)) {
return undefined;
}
if (rule.inferredType) {
return rule.inferredType.name;
}
else if (rule.dataType) {
return rule.dataType;
}
else if (rule.returnType) {
const refType = rule.returnType.ref;
if (refType) {
return refType.name;
}
}
return undefined;
}
export function getTypeName(type) {
if (ast.isAbstractParserRule(type)) {
return ast.isParserRule(type) && isDataTypeRule(type) ? type.name : getExplicitRuleType(type) ?? type.name;
}
else if (ast.isInterface(type) || ast.isType(type) || ast.isReturnType(type)) {
return type.name;
}
else if (ast.isAction(type)) {
const actionType = getActionType(type);
if (actionType) {
return actionType;
}
}
else if (ast.isInferredType(type)) {
return type.name;
}
throw new Error('Cannot get name of Unknown Type');
}
export function getActionType(action) {
if (action.inferredType) {
return action.inferredType.name;
}
else if (action.type?.ref) {
return getTypeName(action.type.ref);
}
return undefined; // not inferring and not referencing a valid type
}
/**
* This function is used at development time (for code generation and the internal type system) to get the type of the AST node produced by the given rule.
* For data type rules, the name of the rule is returned,
* e.g. "INT_value returns number: MY_INT;" returns "INT_value".
* @param rule the given rule
* @returns the name of the AST node type of the rule
*/
export function getRuleTypeName(rule) {
if (ast.isTerminalRule(rule)) {
return rule.type?.name ?? 'string';
}
else {
return ast.isParserRule(rule) && isDataTypeRule(rule) ? rule.name : getExplicitRuleType(rule) ?? rule.name;
}
}
/**
* This function is used at runtime to get the actual type of the values produced by the given rule at runtime.
* For data type rules, the name of the declared return type of the rule is returned (if any),
* e.g. "INT_value returns number: MY_INT;" returns "number".
* @param rule the given rule
* @returns the name of the type of the produced values of the rule at runtime
*/
export function getRuleType(rule) {
if (ast.isTerminalRule(rule)) {
return rule.type?.name ?? 'string';
}
else {
return getExplicitRuleType(rule) ?? rule.name;
}
}
export function terminalRegex(terminalRule) {
const flags = {
s: false,
i: false,
u: false
};
const source = abstractElementToRegex(terminalRule.definition, flags);
const flagText = Object.entries(flags).filter(([, value]) => value).map(([name]) => name).join('');
return new RegExp(source, flagText);
}
// Using [\s\S]* allows to match everything, compared to . which doesn't match line terminators
const WILDCARD = /[\s\S]/.source;
function abstractElementToRegex(element, flags) {
if (ast.isTerminalAlternatives(element)) {
return terminalAlternativesToRegex(element);
}
else if (ast.isTerminalGroup(element)) {
return terminalGroupToRegex(element);
}
else if (ast.isCharacterRange(element)) {
return characterRangeToRegex(element);
}
else if (ast.isTerminalRuleCall(element)) {
const rule = element.rule.ref;
if (!rule) {
throw new Error('Missing rule reference.');
}
return withCardinality(abstractElementToRegex(rule.definition), {
cardinality: element.cardinality,
lookahead: element.lookahead,
parenthesized: element.parenthesized
});
}
else if (ast.isNegatedToken(element)) {
return negateTokenToRegex(element);
}
else if (ast.isUntilToken(element)) {
return untilTokenToRegex(element);
}
else if (ast.isRegexToken(element)) {
const lastSlash = element.regex.lastIndexOf('/');
const source = element.regex.substring(1, lastSlash);
const regexFlags = element.regex.substring(lastSlash + 1);
if (flags) {
flags.i = regexFlags.includes('i');
flags.s = regexFlags.includes('s');
flags.u = regexFlags.includes('u');
}
return withCardinality(source, {
cardinality: element.cardinality,
lookahead: element.lookahead,
parenthesized: element.parenthesized,
wrap: false
});
}
else if (ast.isWildcard(element)) {
return withCardinality(WILDCARD, {
cardinality: element.cardinality,
lookahead: element.lookahead,
parenthesized: element.parenthesized
});
}
else {
throw new Error(`Invalid terminal element: ${element?.$type}, ${element?.$cstNode?.text}`);
}
}
function terminalAlternativesToRegex(alternatives) {
return withCardinality(alternatives.elements.map(e => abstractElementToRegex(e)).join('|'), {
cardinality: alternatives.cardinality,
lookahead: alternatives.lookahead,
parenthesized: alternatives.parenthesized,
wrap: false // wrapping is not required for top level alternatives, and nested alternatives are already parenthesized according to the grammar
});
}
function terminalGroupToRegex(group) {
return withCardinality(group.elements.map(e => abstractElementToRegex(e)).join(''), {
cardinality: group.cardinality,
lookahead: group.lookahead,
parenthesized: group.parenthesized,
wrap: false // wrapping is not required for top level group, and nested group are already parenthesized according to the grammar
});
}
function untilTokenToRegex(until) {
return withCardinality(`${WILDCARD}*?${abstractElementToRegex(until.terminal)}`, {
cardinality: until.cardinality,
lookahead: until.lookahead,
parenthesized: until.parenthesized
});
}
function negateTokenToRegex(negate) {
return withCardinality(`(?!${abstractElementToRegex(negate.terminal)})${WILDCARD}*?`, {
cardinality: negate.cardinality,
lookahead: negate.lookahead,
parenthesized: negate.parenthesized
});
}
function characterRangeToRegex(range) {
if (range.right) {
return withCardinality(`[${keywordToRegex(range.left)}-${keywordToRegex(range.right)}]`, {
cardinality: range.cardinality,
lookahead: range.lookahead,
parenthesized: range.parenthesized,
wrap: false
});
}
return withCardinality(keywordToRegex(range.left), {
cardinality: range.cardinality,
lookahead: range.lookahead,
parenthesized: range.parenthesized,
wrap: false
});
}
function keywordToRegex(keyword) {
return escapeRegExp(keyword.value);
}
function withCardinality(regex, options) {
if (options.parenthesized || options.lookahead || options.wrap !== false) {
const groupConfig = options.lookahead ?? (options.parenthesized ? '' : '?:');
regex = `(${groupConfig}${regex})`;
}
if (options.cardinality) {
return `${regex}${options.cardinality}`;
}
return regex;
}
//# sourceMappingURL=grammar-utils.js.map
File diff suppressed because one or more lines are too long
+21
View File
@@ -0,0 +1,21 @@
/******************************************************************************
* Copyright 2023 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.
******************************************************************************/
export * from './caching.js';
export * from './event.js';
export * from './collections.js';
export * from './disposable.js';
export * from './errors.js';
export * from './grammar-loader.js';
export * from './promise-utils.js';
export * from './stream.js';
export * from './uri-utils.js';
import * as AstUtils from './ast-utils.js';
import * as Cancellation from './cancellation.js';
import * as CstUtils from './cst-utils.js';
import * as GrammarUtils from './grammar-utils.js';
import * as RegExpUtils from './regexp-utils.js';
export { AstUtils, Cancellation, CstUtils, GrammarUtils, RegExpUtils };
//# sourceMappingURL=index.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAEhF,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAE/B,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAC3C,OAAO,KAAK,YAAY,MAAM,mBAAmB,CAAC;AAClD,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAC3C,OAAO,KAAK,YAAY,MAAM,oBAAoB,CAAC;AACnD,OAAO,KAAK,WAAW,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,QAAQ,EAAE,YAAY,EAAE,WAAW,EAAE,CAAC"}
+21
View File
@@ -0,0 +1,21 @@
/******************************************************************************
* Copyright 2023 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.
******************************************************************************/
export * from './caching.js';
export * from './event.js';
export * from './collections.js';
export * from './disposable.js';
export * from './errors.js';
export * from './grammar-loader.js';
export * from './promise-utils.js';
export * from './stream.js';
export * from './uri-utils.js';
import * as AstUtils from './ast-utils.js';
import * as Cancellation from './cancellation.js';
import * as CstUtils from './cst-utils.js';
import * as GrammarUtils from './grammar-utils.js';
import * as RegExpUtils from './regexp-utils.js';
export { AstUtils, Cancellation, CstUtils, GrammarUtils, RegExpUtils };
//# sourceMappingURL=index.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAEhF,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAE/B,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAC3C,OAAO,KAAK,YAAY,MAAM,mBAAmB,CAAC;AAClD,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAC3C,OAAO,KAAK,YAAY,MAAM,oBAAoB,CAAC;AACnD,OAAO,KAAK,WAAW,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,QAAQ,EAAE,YAAY,EAAE,WAAW,EAAE,CAAC"}
+54
View File
@@ -0,0 +1,54 @@
/******************************************************************************
* 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 { CancellationToken, type AbstractCancellationTokenSource } from '../utils/cancellation.js';
export type MaybePromise<T> = T | Promise<T>;
/**
* Delays the execution of the current code to the next tick of the event loop.
* Don't call this method directly in a tight loop to prevent too many promises from being created.
*/
export declare function delayNextTick(): Promise<void>;
/**
* Reset the global interruption period and create a cancellation token source.
*/
export declare function startCancelableOperation(): AbstractCancellationTokenSource;
/**
* Change the period duration for `interruptAndCheck` to the given number of milliseconds.
* The default value is 10ms.
*/
export declare function setInterruptionPeriod(period: number): void;
/**
* This symbol may be thrown in an asynchronous context by any Langium service that receives
* a `CancellationToken`. This means that the promise returned by such a service is rejected with
* this symbol as rejection reason.
*/
export declare const OperationCancelled: unique symbol;
/**
* Use this in a `catch` block to check whether the thrown object indicates that the operation
* has been cancelled.
*/
export declare function isOperationCancelled(err: unknown): err is typeof OperationCancelled;
/**
* This function does two things:
* 1. Check the elapsed time since the last call to this function or to `startCancelableOperation`. If the predefined
* period (configured with `setInterruptionPeriod`) is exceeded, execution is delayed with `delayNextTick`.
* 2. If the predefined period is not met yet or execution is resumed after an interruption, the given cancellation
* token is checked, and if cancellation is requested, `OperationCanceled` is thrown.
*
* All services in Langium that receive a `CancellationToken` may potentially call this function, so the
* `CancellationToken` must be caught (with an `async` try-catch block or a `catch` callback attached to
* the promise) to avoid that event being exposed as an error.
*/
export declare function interruptAndCheck(token: CancellationToken): Promise<void>;
/**
* Simple implementation of the deferred pattern.
* An object that exposes a promise and functions to resolve and reject it.
*/
export declare class Deferred<T = void> {
resolve: (value: T) => this;
reject: (err?: unknown) => this;
promise: Promise<T>;
}
//# sourceMappingURL=promise-utils.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"promise-utils.d.ts","sourceRoot":"","sources":["../../src/utils/promise-utils.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAEhF,OAAO,EAAE,iBAAiB,EAA2B,KAAK,+BAA+B,EAAE,MAAM,0BAA0B,CAAC;AAE5H,MAAM,MAAM,YAAY,CAAC,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;AAE5C;;;GAGG;AACH,wBAAgB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAU7C;AAKD;;GAEG;AACH,wBAAgB,wBAAwB,IAAI,+BAA+B,CAG1E;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAE1D;AAED;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,eAA+B,CAAC;AAE/D;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,OAAO,kBAAkB,CAEnF;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAiB/E;AAED;;;GAGG;AACH,qBAAa,QAAQ,CAAC,CAAC,GAAG,IAAI;IAC1B,OAAO,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;IAC5B,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IAEhC,OAAO,aASJ;CACN"}
+99
View File
@@ -0,0 +1,99 @@
/******************************************************************************
* 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 { CancellationToken, CancellationTokenSource } from '../utils/cancellation.js';
/**
* Delays the execution of the current code to the next tick of the event loop.
* Don't call this method directly in a tight loop to prevent too many promises from being created.
*/
export function delayNextTick() {
return new Promise(resolve => {
// In case we are running in a non-node environment, `setImmediate` isn't available.
// Using `setTimeout` of the browser API accomplishes the same result.
if (typeof setImmediate === 'undefined') {
setTimeout(resolve, 0);
}
else {
setImmediate(resolve);
}
});
}
let lastTick = 0;
let globalInterruptionPeriod = 10;
/**
* Reset the global interruption period and create a cancellation token source.
*/
export function startCancelableOperation() {
lastTick = performance.now();
return new CancellationTokenSource();
}
/**
* Change the period duration for `interruptAndCheck` to the given number of milliseconds.
* The default value is 10ms.
*/
export function setInterruptionPeriod(period) {
globalInterruptionPeriod = period;
}
/**
* This symbol may be thrown in an asynchronous context by any Langium service that receives
* a `CancellationToken`. This means that the promise returned by such a service is rejected with
* this symbol as rejection reason.
*/
export const OperationCancelled = Symbol('OperationCancelled');
/**
* Use this in a `catch` block to check whether the thrown object indicates that the operation
* has been cancelled.
*/
export function isOperationCancelled(err) {
return err === OperationCancelled;
}
/**
* This function does two things:
* 1. Check the elapsed time since the last call to this function or to `startCancelableOperation`. If the predefined
* period (configured with `setInterruptionPeriod`) is exceeded, execution is delayed with `delayNextTick`.
* 2. If the predefined period is not met yet or execution is resumed after an interruption, the given cancellation
* token is checked, and if cancellation is requested, `OperationCanceled` is thrown.
*
* All services in Langium that receive a `CancellationToken` may potentially call this function, so the
* `CancellationToken` must be caught (with an `async` try-catch block or a `catch` callback attached to
* the promise) to avoid that event being exposed as an error.
*/
export async function interruptAndCheck(token) {
if (token === CancellationToken.None) {
// Early exit in case cancellation was disabled by the caller
return;
}
const current = performance.now();
if (current - lastTick >= globalInterruptionPeriod) {
lastTick = current;
await delayNextTick();
// prevent calling delayNextTick every iteration of loop
// where delayNextTick takes up the majority or all of the
// globalInterruptionPeriod itself
lastTick = performance.now();
}
if (token.isCancellationRequested) {
throw OperationCancelled;
}
}
/**
* Simple implementation of the deferred pattern.
* An object that exposes a promise and functions to resolve and reject it.
*/
export class Deferred {
constructor() {
this.promise = new Promise((resolve, reject) => {
this.resolve = (arg) => {
resolve(arg);
return this;
};
this.reject = (err) => {
reject(err);
return this;
};
});
}
}
//# sourceMappingURL=promise-utils.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"promise-utils.js","sourceRoot":"","sources":["../../src/utils/promise-utils.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAEhF,OAAO,EAAE,iBAAiB,EAAE,uBAAuB,EAAwC,MAAM,0BAA0B,CAAC;AAI5H;;;GAGG;AACH,MAAM,UAAU,aAAa;IACzB,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE;QACzB,oFAAoF;QACpF,sEAAsE;QACtE,IAAI,OAAO,YAAY,KAAK,WAAW,EAAE,CAAC;YACtC,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QAC3B,CAAC;aAAM,CAAC;YACJ,YAAY,CAAC,OAAO,CAAC,CAAC;QAC1B,CAAC;IACL,CAAC,CAAC,CAAC;AACP,CAAC;AAED,IAAI,QAAQ,GAAG,CAAC,CAAC;AACjB,IAAI,wBAAwB,GAAG,EAAE,CAAC;AAElC;;GAEG;AACH,MAAM,UAAU,wBAAwB;IACpC,QAAQ,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;IAC7B,OAAO,IAAI,uBAAuB,EAAE,CAAC;AACzC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CAAC,MAAc;IAChD,wBAAwB,GAAG,MAAM,CAAC;AACtC,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,MAAM,CAAC,oBAAoB,CAAC,CAAC;AAE/D;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAC,GAAY;IAC7C,OAAO,GAAG,KAAK,kBAAkB,CAAC;AACtC,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,KAAwB;IAC5D,IAAI,KAAK,KAAK,iBAAiB,CAAC,IAAI,EAAE,CAAC;QACnC,6DAA6D;QAC7D,OAAO;IACX,CAAC;IACD,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;IAClC,IAAI,OAAO,GAAG,QAAQ,IAAI,wBAAwB,EAAE,CAAC;QACjD,QAAQ,GAAG,OAAO,CAAC;QACnB,MAAM,aAAa,EAAE,CAAC;QACtB,wDAAwD;QACxD,0DAA0D;QAC1D,kCAAkC;QAClC,QAAQ,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;IACjC,CAAC;IACD,IAAI,KAAK,CAAC,uBAAuB,EAAE,CAAC;QAChC,MAAM,kBAAkB,CAAC;IAC7B,CAAC;AACL,CAAC;AAED;;;GAGG;AACH,MAAM,OAAO,QAAQ;IAArB;QAII,YAAO,GAAG,IAAI,OAAO,CAAI,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACzC,IAAI,CAAC,OAAO,GAAG,CAAC,GAAG,EAAE,EAAE;gBACnB,OAAO,CAAC,GAAG,CAAC,CAAC;gBACb,OAAO,IAAI,CAAC;YAChB,CAAC,CAAC;YACF,IAAI,CAAC,MAAM,GAAG,CAAC,GAAG,EAAE,EAAE;gBAClB,MAAM,CAAC,GAAG,CAAC,CAAC;gBACZ,OAAO,IAAI,CAAC;YAChB,CAAC,CAAC;QACN,CAAC,CAAC,CAAC;IACP,CAAC;CAAA"}
+33
View File
@@ -0,0 +1,33 @@
/******************************************************************************
* 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.
******************************************************************************/
export declare const NEWLINE_REGEXP: RegExp;
export declare function getTerminalParts(regexp: RegExp | string): Array<{
start: string;
end: string;
}>;
export declare function isMultilineComment(regexp: RegExp | string): boolean;
/**
* A set of all characters that are considered whitespace by the '\s' RegExp character class.
* Taken from [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions/Character_classes).
*/
export declare const whitespaceCharacters: string[];
export declare function isWhitespace(value: RegExp | string): boolean;
export declare function escapeRegExp(value: string): string;
/**
* Determines whether the given input has a partial match with the specified regex.
* @param regex The regex to partially match against
* @param input The input string
* @returns Whether any match exists.
*/
export declare function partialMatches(regex: RegExp | string, input: string): boolean;
/**
* Builds a partial regex from the input regex. A partial regex is able to match incomplete input strings. E.g.
* a partial regex constructed from `/ab/` is able to match the string `a` without needing a following `b` character. However it won't match `b` alone.
* @param regex The input regex to be converted.
* @returns A partial regex constructed from the input regex.
*/
export declare function partialRegExp(regex: RegExp | string): RegExp;
//# sourceMappingURL=regexp-utils.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"regexp-utils.d.ts","sourceRoot":"","sources":["../../src/utils/regexp-utils.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAKhF,eAAO,MAAM,cAAc,QAAY,CAAC;AA8FxC,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,CAoB/F;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAanE;AAED;;;GAGG;AACH,eAAO,MAAM,oBAAoB,UAEsC,CAAC;AAExE,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAG5D;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAElD;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAI7E;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAyI5D"}
+294
View File
@@ -0,0 +1,294 @@
/******************************************************************************
* 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 { RegExpParser, BaseRegExpVisitor } from '@chevrotain/regexp-to-ast';
export const NEWLINE_REGEXP = /\r?\n/gm;
const regexpParser = new RegExpParser();
/**
* This class is in charge of heuristically identifying start/end tokens of terminals.
*
* The way this works is by doing the following:
* 1. Traverse the regular expression in the "start state"
* 2. Add any encountered sets/single characters to the "start regexp"
* 3. Once we encounter any variable-length content (i.e. with quantifiers such as +/?/*), we enter the "end state"
* 4. In the end state, any sets/single characters are added to an "end stack".
* 5. If we re-encounter any variable-length content we reset the end stack
* 6. We continue visiting the regex until the end, reseting the end stack and rebuilding it as necessary
*
* After traversing a regular expression the `startRegexp/endRegexp` properties allow access to the stored start/end of the terminal
*/
class TerminalRegExpVisitor extends BaseRegExpVisitor {
constructor() {
super(...arguments);
this.isStarting = true;
this.endRegexpStack = [];
this.multiline = false;
}
get endRegex() {
return this.endRegexpStack.join('');
}
reset(regex) {
this.multiline = false;
this.regex = regex;
this.startRegexp = '';
this.isStarting = true;
this.endRegexpStack = [];
}
visitGroup(node) {
if (node.quantifier) {
this.isStarting = false;
this.endRegexpStack = [];
}
}
visitCharacter(node) {
const char = String.fromCharCode(node.value);
if (!this.multiline && char === '\n') {
this.multiline = true;
}
if (node.quantifier) {
this.isStarting = false;
this.endRegexpStack = [];
}
else {
const escapedChar = escapeRegExp(char);
this.endRegexpStack.push(escapedChar);
if (this.isStarting) {
this.startRegexp += escapedChar;
}
}
}
visitSet(node) {
if (!this.multiline) {
const set = this.regex.substring(node.loc.begin, node.loc.end);
const regex = new RegExp(set);
this.multiline = Boolean('\n'.match(regex));
}
if (node.quantifier) {
this.isStarting = false;
this.endRegexpStack = [];
}
else {
const set = this.regex.substring(node.loc.begin, node.loc.end);
this.endRegexpStack.push(set);
if (this.isStarting) {
this.startRegexp += set;
}
}
}
visitChildren(node) {
if (node.type === 'Group') {
// Ignore children of groups with quantifier (+/*/?)
// These groups are unrelated to start/end tokens of terminals
const group = node;
if (group.quantifier) {
return;
}
}
super.visitChildren(node);
}
}
const visitor = new TerminalRegExpVisitor();
export function getTerminalParts(regexp) {
try {
if (typeof regexp !== 'string') {
regexp = regexp.source;
}
regexp = `/${regexp}/`;
const pattern = regexpParser.pattern(regexp);
const parts = [];
for (const alternative of pattern.value.value) {
visitor.reset(regexp);
visitor.visit(alternative);
parts.push({
start: visitor.startRegexp,
end: visitor.endRegex
});
}
return parts;
}
catch {
return [];
}
}
export function isMultilineComment(regexp) {
try {
if (typeof regexp === 'string') {
regexp = new RegExp(regexp);
}
regexp = regexp.toString();
visitor.reset(regexp);
// Parsing the pattern might fail (since it's user code)
visitor.visit(regexpParser.pattern(regexp));
return visitor.multiline;
}
catch {
return false;
}
}
/**
* A set of all characters that are considered whitespace by the '\s' RegExp character class.
* Taken from [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions/Character_classes).
*/
export const whitespaceCharacters = ('\f\n\r\t\v\u0020\u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007' +
'\u2008\u2009\u200a\u2028\u2029\u202f\u205f\u3000\ufeff').split('');
export function isWhitespace(value) {
const regexp = typeof value === 'string' ? new RegExp(value) : value;
return whitespaceCharacters.some((ws) => regexp.test(ws));
}
export function escapeRegExp(value) {
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
/**
* Determines whether the given input has a partial match with the specified regex.
* @param regex The regex to partially match against
* @param input The input string
* @returns Whether any match exists.
*/
export function partialMatches(regex, input) {
const partial = partialRegExp(regex);
const match = input.match(partial);
return !!match && match[0].length > 0;
}
/**
* Builds a partial regex from the input regex. A partial regex is able to match incomplete input strings. E.g.
* a partial regex constructed from `/ab/` is able to match the string `a` without needing a following `b` character. However it won't match `b` alone.
* @param regex The input regex to be converted.
* @returns A partial regex constructed from the input regex.
*/
export function partialRegExp(regex) {
if (typeof regex === 'string') {
regex = new RegExp(regex);
}
const re = regex, source = regex.source;
let i = 0;
function process() {
let result = '', tmp;
function appendRaw(nbChars) {
result += source.substr(i, nbChars);
i += nbChars;
}
function appendOptional(nbChars) {
result += '(?:' + source.substr(i, nbChars) + '|$)';
i += nbChars;
}
while (i < source.length) {
switch (source[i]) {
case '\\':
switch (source[i + 1]) {
case 'c':
appendOptional(3);
break;
case 'x':
appendOptional(4);
break;
case 'u':
if (re.unicode) {
if (source[i + 2] === '{') {
appendOptional(source.indexOf('}', i) - i + 1);
}
else {
appendOptional(6);
}
}
else {
appendOptional(2);
}
break;
case 'p':
case 'P':
if (re.unicode) {
appendOptional(source.indexOf('}', i) - i + 1);
}
else {
appendOptional(2);
}
break;
case 'k':
appendOptional(source.indexOf('>', i) - i + 1);
break;
default:
appendOptional(2);
break;
}
break;
case '[':
tmp = /\[(?:\\.|.)*?\]/g;
tmp.lastIndex = i;
tmp = tmp.exec(source) || [];
appendOptional(tmp[0].length);
break;
case '|':
case '^':
case '$':
case '*':
case '+':
case '?':
appendRaw(1);
break;
case '{':
tmp = /\{\d+,?\d*\}/g;
tmp.lastIndex = i;
tmp = tmp.exec(source);
if (tmp) {
appendRaw(tmp[0].length);
}
else {
appendOptional(1);
}
break;
case '(':
if (source[i + 1] === '?') {
switch (source[i + 2]) {
case ':':
result += '(?:';
i += 3;
result += process() + '|$)';
break;
case '=':
result += '(?=';
i += 3;
result += process() + ')';
break;
case '!':
tmp = i;
i += 3;
process();
result += source.substr(tmp, i - tmp);
break;
case '<':
switch (source[i + 3]) {
case '=':
case '!':
tmp = i;
i += 4;
process();
result += source.substr(tmp, i - tmp);
break;
default:
appendRaw(source.indexOf('>', i) - i + 1);
result += process() + '|$)';
break;
}
break;
}
}
else {
appendRaw(1);
result += process() + '|$)';
}
break;
case ')':
++i;
return result;
default:
appendOptional(1);
break;
}
}
return result;
}
return new RegExp(process(), regex.flags);
}
//# sourceMappingURL=regexp-utils.js.map
File diff suppressed because one or more lines are too long
+329
View File
@@ -0,0 +1,329 @@
/******************************************************************************
* 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.
******************************************************************************/
/**
* A stream is a read-only sequence of values. While the contents of an array can be accessed
* both sequentially and randomly (via index), a stream allows only sequential access.
*
* The advantage of this is that a stream can be evaluated lazily, so it does not require
* to store intermediate values. This can boost performance when a large sequence is
* processed via filtering, mapping etc. and accessed at most once. However, lazy
* evaluation means that all processing is repeated when you access the sequence multiple
* times; in such a case, it may be better to store the resulting sequence into an array.
*/
export interface Stream<T> extends Iterable<T> {
/**
* Returns an iterator for this stream. This is the same as calling the `Symbol.iterator` function property.
*/
iterator(): IterableIterator<T>;
/**
* Determines whether this stream contains no elements.
*/
isEmpty(): boolean;
/**
* Determines the number of elements in this stream.
*/
count(): number;
/**
* Collects all elements of this stream into an array.
*/
toArray(): T[];
/**
* Collects all elements of this stream into a Set.
*/
toSet(): Set<T>;
/**
* Collects all elements of this stream into a Map, applying the provided functions to determine keys and values.
*
* @param keyFn The function to derive map keys. If omitted, the stream elements are used as keys.
* @param valueFn The function to derive map values. If omitted, the stream elements are used as values.
*/
toMap<K = T, V = T>(keyFn?: (e: T) => K, valueFn?: (e: T) => V): Map<K, V>;
/**
* Returns a string representation of a stream.
*/
toString(): string;
/**
* Combines two streams by returning a new stream that yields all elements of this stream and the other stream.
*
* @param other Stream to be concatenated with this one.
*/
concat<T2>(other: Iterable<T2>): Stream<T | T2>;
/**
* Adds all elements of the stream into a string, separated by the specified separator string.
*
* @param separator A string used to separate one element of the stream from the next in the resulting string.
* If omitted, the steam elements are separated with a comma.
*/
join(separator?: string): string;
/**
* Returns the index of the first occurrence of a value in the stream, or -1 if it is not present.
*
* @param searchElement The value to locate in the array.
* @param fromIndex The stream index at which to begin the search. If fromIndex is omitted, the search
* starts at index 0.
*/
indexOf(searchElement: T, fromIndex?: number): number;
/**
* Determines whether all members of the stream satisfy the specified test.
*
* @param predicate This method calls the predicate function for each element in the stream until the
* predicate returns a value which is coercible to the Boolean value `false`, or until the end
* of the stream.
*/
every<S extends T>(predicate: (value: T) => value is S): this is Stream<S>;
every(predicate: (value: T) => unknown): boolean;
/**
* Determines whether any member of the stream satisfies the specified test.
*
* @param predicate This method calls the predicate function for each element in the stream until the
* predicate returns a value which is coercible to the Boolean value `true`, or until the end
* of the stream.
*/
some(predicate: (value: T) => unknown): boolean;
/**
* Performs the specified action for each element in the stream.
*
* @param callbackfn Function called once for each element in the stream.
*/
forEach(callbackfn: (value: T, index: number) => void): void;
/**
* Returns a stream that yields the results of calling the specified callback function on each element
* of the stream. The function is called when the resulting stream elements are actually accessed, so
* accessing the resulting stream multiple times means the function is also called multiple times for
* each element of the stream.
*
* @param callbackfn Lazily evaluated function mapping stream elements.
*/
map<U>(callbackfn: (value: T) => U): Stream<U>;
/**
* Returns the elements of the stream that meet the condition specified in a callback function.
* The function is called when the resulting stream elements are actually accessed, so accessing the
* resulting stream multiple times means the function is also called multiple times for each element
* of the stream.
*
* @param predicate Lazily evaluated function checking a condition on stream elements.
*/
filter<S extends T>(predicate: (value: T) => value is S): Stream<S>;
filter(predicate: (value: T) => unknown): Stream<T>;
/**
* Returns the elements of the stream that are _non-nullable_, which means they are neither `undefined`
* nor `null`.
*/
nonNullable(): Stream<NonNullable<T>>;
/**
* Calls the specified callback function for all elements in the stream. The return value of the
* callback function is the accumulated result, and is provided as an argument in the next call to
* the callback function.
*
* @param callbackfn This method calls the function once for each element in the stream, providing
* the previous and current values of the reduction.
* @param initialValue If specified, `initialValue` is used as the initial value to start the
* accumulation. The first call to the function provides this value as an argument instead
* of a stream value.
*/
reduce(callbackfn: (previousValue: T, currentValue: T) => T): T | undefined;
reduce<U = T>(callbackfn: (previousValue: U, currentValue: T) => U, initialValue: U): U;
/**
* Calls the specified callback function for all elements in the stream, in descending order.
* The return value of the callback function is the accumulated result, and is provided as an
* argument in the next call to the callback function.
*
* @param callbackfn This method calls the function once for each element in the stream, providing
* the previous and current values of the reduction.
* @param initialValue If specified, `initialValue` is used as the initial value to start the
* accumulation. The first call to the function provides this value as an argument instead
* of an array value.
*/
reduceRight(callbackfn: (previousValue: T, currentValue: T) => T): T | undefined;
reduceRight<U = T>(callbackfn: (previousValue: U, currentValue: T) => U, initialValue: U): U;
/**
* Returns the value of the first element in the stream that meets the condition, or `undefined`
* if there is no such element.
*
* @param predicate This method calls `predicate` once for each element of the stream, in ascending
* order, until it finds one where `predicate` returns a value which is coercible to the
* Boolean value `true`.
*/
find<S extends T>(predicate: (value: T) => value is S): S | undefined;
find(predicate: (value: T) => unknown): T | undefined;
/**
* Returns the index of the first element in the stream that meets the condition, or `-1`
* if there is no such element.
*
* @param predicate This method calls `predicate` once for each element of the stream, in ascending
* order, until it finds one where `predicate` returns a value which is coercible to the
* Boolean value `true`.
*/
findIndex(predicate: (value: T) => unknown): number;
/**
* Determines whether the stream includes a certain element, returning `true` or `false` as appropriate.
*
* @param searchElement The element to search for.
*/
includes(searchElement: T): boolean;
/**
* Calls a defined callback function on each element of the stream and then flattens the result into
* a new stream. This is identical to a `map` followed by `flat` with depth 1.
*
* @param callbackfn Lazily evaluated function mapping stream elements.
*/
flatMap<U>(callbackfn: (value: T) => U | Iterable<U>): Stream<U>;
/**
* Returns a new stream with all sub-stream or sub-array elements concatenated into it recursively up
* to the specified depth.
*
* @param depth The maximum recursion depth. Defaults to 1.
*/
flat<D extends number = 1>(depth?: D): FlatStream<T, D>;
/**
* Returns the first element in the stream, or `undefined` if the stream is empty.
*/
head(): T | undefined;
/**
* Returns a stream that skips the first `skipCount` elements from this stream.
*
* @param skipCount The number of elements to skip. If this is larger than the number of elements in
* the stream, an empty stream is returned. Defaults to 1.
*/
tail(skipCount?: number): Stream<T>;
/**
* Returns a stream consisting of the elements of this stream, truncated to be no longer than `maxSize`
* in length.
*
* @param maxSize The number of elements the stream should be limited to
*/
limit(maxSize: number): Stream<T>;
/**
* Returns a stream containing only the distinct elements from this stream.
* Equality is determined with the same rules as a standard `Set`.
*
* @param by A function returning the key used to check equality with a previous stream element.
* If omitted, the stream elements themselves are used for comparison.
*/
distinct<Key = T>(by?: (element: T) => Key): Stream<T>;
/**
* Returns a stream that contains all elements that don't exist in the {@link other} iterable.
* Equality is determined with the same rules as a standard `Set`.
* @param other The elements that should be exluded from this stream.
* @param key A function returning the key used to check quality.
* If omitted, the stream elements themselves are used for comparison.
*/
exclude<Key = T>(other: Iterable<T>, key?: (element: T) => Key): Stream<T>;
}
export type FlatStream<T, Depth extends number> = {
'done': Stream<T>;
'recur': T extends Iterable<infer Content> ? FlatStream<Content, MinusOne<Depth>> : Stream<T>;
}[Depth extends 0 ? 'done' : 'recur'];
export type MinusOne<N extends number> = [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20][N];
/**
* The default implementation of `Stream` works with two input functions:
* - The first function creates the initial state of an iteration.
* - The second function gets the current state as argument and returns an `IteratorResult`.
*/
export declare class StreamImpl<S, T> implements Stream<T> {
protected readonly startFn: () => S;
protected readonly nextFn: (state: S) => IteratorResult<T>;
constructor(startFn: () => S, nextFn: (state: S) => IteratorResult<T, undefined>);
iterator(): IterableIterator<T>;
[Symbol.iterator](): Iterator<T>;
isEmpty(): boolean;
count(): number;
toArray(): T[];
toSet(): Set<T>;
toMap<K = T, V = T>(keyFn?: (e: T) => K, valueFn?: (e: T) => V): Map<K, V>;
toString(): string;
concat<T2>(other: Iterable<T2>): Stream<T | T2>;
join(separator?: string): string;
indexOf(searchElement: T, fromIndex?: number): number;
every<U extends T>(predicate: (value: T) => value is U): this is Stream<U> & this;
every(predicate: (value: T) => unknown): boolean;
some(predicate: (value: T) => unknown): boolean;
forEach(callbackfn: (value: T, index: number) => void): void;
map<U>(callbackfn: (value: T) => U): Stream<U>;
filter<U extends T>(predicate: (value: T) => value is U): Stream<U> & this;
filter(predicate: (value: T) => unknown): Stream<T> & this;
nonNullable(): Stream<NonNullable<T>>;
reduce(callbackfn: (previousValue: T, currentValue: T) => T): T | undefined;
reduce<U = T>(callbackfn: (previousValue: U, currentValue: T) => U, initialValue: U): U;
reduceRight(callbackfn: (previousValue: T, currentValue: T) => T): T | undefined;
reduceRight<U = T>(callbackfn: (previousValue: U, currentValue: T) => U, initialValue: U): U;
protected recursiveReduce<U>(iterator: Iterator<T>, callbackfn: (previousValue: U | T, currentValue: T) => U, initialValue?: U): U | T | undefined;
find<S extends T>(predicate: (value: T) => value is S): S | undefined;
find(predicate: (value: T) => unknown): T | undefined;
findIndex(predicate: (value: T) => unknown): number;
includes(searchElement: T): boolean;
flatMap<U>(callbackfn: (value: T) => U | Iterable<U>): Stream<U>;
flat<D extends number = 1>(depth?: D): FlatStream<T, D>;
head(): T | undefined;
tail(skipCount?: number): Stream<T>;
limit(maxSize: number): Stream<T>;
distinct<Key = T>(by?: (element: T) => Key): Stream<T>;
exclude<Key = T>(other: Iterable<T>, key?: (element: T) => Key): Stream<T>;
}
/**
* An empty stream of any type.
*/
export declare const EMPTY_STREAM: Stream<any>;
/**
* Use this `IteratorResult` when implementing a `StreamImpl` to indicate that there are no more elements in the stream.
*/
export declare const DONE_RESULT: IteratorReturnResult<undefined>;
/**
* Create a stream from one or more iterables or array-likes.
*/
export declare function stream<T>(...collections: Array<Iterable<T> | ArrayLike<T>>): Stream<T>;
/**
* A tree iterator adds the ability to prune the current iteration.
*/
export interface TreeIterator<T> extends IterableIterator<T> {
/**
* Skip the whole subtree below the last returned element. The iteration continues as if that
* element had no children.
*/
prune(): void;
}
/**
* A tree stream is used to stream the elements of a tree, for example an AST or CST.
*/
export interface TreeStream<T> extends Stream<T> {
iterator(): TreeIterator<T>;
}
/**
* The default implementation of `TreeStream` takes a root element and a function that computes the
* children of its argument. Whether the root node included in the stream is controlled with the
* `includeRoot` option, which defaults to `false`.
*/
export declare class TreeStreamImpl<T> extends StreamImpl<{
iterators: Array<Iterator<T>>;
pruned: boolean;
}, T> implements TreeStream<T> {
constructor(root: T, children: (node: T) => Iterable<T>, options?: {
includeRoot?: boolean;
});
iterator(): TreeIterator<T>;
}
/**
* A set of utility functions that reduce a stream to a single value.
*/
export declare namespace Reduction {
/**
* Compute the sum of a number stream.
*/
function sum(stream: Stream<number>): number;
/**
* Compute the product of a number stream.
*/
function product(stream: Stream<number>): number;
/**
* Compute the minimum of a number stream. Returns `undefined` if the stream is empty.
*/
function min(stream: Stream<number>): number | undefined;
/**
* Compute the maximum of a number stream. Returns `undefined` if the stream is empty.
*/
function max(stream: Stream<number>): number | undefined;
}
//# sourceMappingURL=stream.d.ts.map
File diff suppressed because one or more lines are too long
+510
View File
@@ -0,0 +1,510 @@
/******************************************************************************
* 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.
******************************************************************************/
/**
* The default implementation of `Stream` works with two input functions:
* - The first function creates the initial state of an iteration.
* - The second function gets the current state as argument and returns an `IteratorResult`.
*/
export class StreamImpl {
constructor(startFn, nextFn) {
this.startFn = startFn;
this.nextFn = nextFn;
}
iterator() {
const iterator = {
state: this.startFn(),
next: () => this.nextFn(iterator.state),
[Symbol.iterator]: () => iterator
};
return iterator;
}
[Symbol.iterator]() {
return this.iterator();
}
isEmpty() {
const iterator = this.iterator();
return Boolean(iterator.next().done);
}
count() {
const iterator = this.iterator();
let count = 0;
let next = iterator.next();
while (!next.done) {
count++;
next = iterator.next();
}
return count;
}
toArray() {
const result = [];
const iterator = this.iterator();
let next;
do {
next = iterator.next();
if (next.value !== undefined) {
result.push(next.value);
}
} while (!next.done);
return result;
}
toSet() {
return new Set(this);
}
toMap(keyFn, valueFn) {
const entryStream = this.map(element => [
keyFn ? keyFn(element) : element,
valueFn ? valueFn(element) : element
]);
return new Map(entryStream);
}
toString() {
return this.join();
}
concat(other) {
return new StreamImpl(() => ({ first: this.startFn(), firstDone: false, iterator: other[Symbol.iterator]() }), state => {
let result;
if (!state.firstDone) {
do {
result = this.nextFn(state.first);
if (!result.done) {
return result;
}
} while (!result.done);
state.firstDone = true;
}
do {
result = state.iterator.next();
if (!result.done) {
return result;
}
} while (!result.done);
return DONE_RESULT;
});
}
join(separator = ',') {
const iterator = this.iterator();
let value = '';
let result;
let addSeparator = false;
do {
result = iterator.next();
if (!result.done) {
if (addSeparator) {
value += separator;
}
value += toString(result.value);
}
addSeparator = true;
} while (!result.done);
return value;
}
indexOf(searchElement, fromIndex = 0) {
const iterator = this.iterator();
let index = 0;
let next = iterator.next();
while (!next.done) {
if (index >= fromIndex && next.value === searchElement) {
return index;
}
next = iterator.next();
index++;
}
return -1;
}
every(predicate) {
const iterator = this.iterator();
let next = iterator.next();
while (!next.done) {
if (!predicate(next.value)) {
return false;
}
next = iterator.next();
}
return true;
}
some(predicate) {
const iterator = this.iterator();
let next = iterator.next();
while (!next.done) {
if (predicate(next.value)) {
return true;
}
next = iterator.next();
}
return false;
}
forEach(callbackfn) {
const iterator = this.iterator();
let index = 0;
let next = iterator.next();
while (!next.done) {
callbackfn(next.value, index);
next = iterator.next();
index++;
}
}
map(callbackfn) {
return new StreamImpl(this.startFn, (state) => {
const { done, value } = this.nextFn(state);
if (done) {
return DONE_RESULT;
}
else {
return { done: false, value: callbackfn(value) };
}
});
}
filter(predicate) {
return new StreamImpl(this.startFn, state => {
let result;
do {
result = this.nextFn(state);
if (!result.done && predicate(result.value)) {
return result;
}
} while (!result.done);
return DONE_RESULT;
});
}
nonNullable() {
return this.filter(e => e !== undefined && e !== null);
}
reduce(callbackfn, initialValue) {
const iterator = this.iterator();
let previousValue = initialValue;
let next = iterator.next();
while (!next.done) {
if (previousValue === undefined) {
previousValue = next.value;
}
else {
previousValue = callbackfn(previousValue, next.value);
}
next = iterator.next();
}
return previousValue;
}
reduceRight(callbackfn, initialValue) {
return this.recursiveReduce(this.iterator(), callbackfn, initialValue);
}
recursiveReduce(iterator, callbackfn, initialValue) {
const next = iterator.next();
if (next.done) {
return initialValue;
}
const previousValue = this.recursiveReduce(iterator, callbackfn, initialValue);
if (previousValue === undefined) {
return next.value;
}
return callbackfn(previousValue, next.value);
}
find(predicate) {
const iterator = this.iterator();
let next = iterator.next();
while (!next.done) {
if (predicate(next.value)) {
return next.value;
}
next = iterator.next();
}
return undefined;
}
findIndex(predicate) {
const iterator = this.iterator();
let index = 0;
let next = iterator.next();
while (!next.done) {
if (predicate(next.value)) {
return index;
}
next = iterator.next();
index++;
}
return -1;
}
includes(searchElement) {
const iterator = this.iterator();
let next = iterator.next();
while (!next.done) {
if (next.value === searchElement) {
return true;
}
next = iterator.next();
}
return false;
}
flatMap(callbackfn) {
return new StreamImpl(() => ({ this: this.startFn() }), (state) => {
do {
if (state.iterator) {
const next = state.iterator.next();
if (next.done) {
state.iterator = undefined;
}
else {
return next;
}
}
const { done, value } = this.nextFn(state.this);
if (!done) {
const mapped = callbackfn(value);
if (isIterable(mapped)) {
state.iterator = mapped[Symbol.iterator]();
}
else {
return { done: false, value: mapped };
}
}
} while (state.iterator);
return DONE_RESULT;
});
}
flat(depth) {
if (depth === undefined) {
depth = 1;
}
if (depth <= 0) {
return this;
}
const stream = depth > 1 ? this.flat(depth - 1) : this;
return new StreamImpl(() => ({ this: stream.startFn() }), (state) => {
do {
if (state.iterator) {
const next = state.iterator.next();
if (next.done) {
state.iterator = undefined;
}
else {
return next;
}
}
const { done, value } = stream.nextFn(state.this);
if (!done) {
if (isIterable(value)) {
state.iterator = value[Symbol.iterator]();
}
else {
return { done: false, value: value };
}
}
} while (state.iterator);
return DONE_RESULT;
});
}
head() {
const iterator = this.iterator();
const result = iterator.next();
if (result.done) {
return undefined;
}
return result.value;
}
tail(skipCount = 1) {
return new StreamImpl(() => {
const state = this.startFn();
for (let i = 0; i < skipCount; i++) {
const next = this.nextFn(state);
if (next.done) {
return state;
}
}
return state;
}, this.nextFn);
}
limit(maxSize) {
return new StreamImpl(() => ({ size: 0, state: this.startFn() }), state => {
state.size++;
if (state.size > maxSize) {
return DONE_RESULT;
}
return this.nextFn(state.state);
});
}
distinct(by) {
return new StreamImpl(() => ({ set: new Set(), internalState: this.startFn() }), state => {
let result;
do {
result = this.nextFn(state.internalState);
if (!result.done) {
const value = by ? by(result.value) : result.value;
if (!state.set.has(value)) {
state.set.add(value);
return result;
}
}
} while (!result.done);
return DONE_RESULT;
});
}
exclude(other, key) {
const otherKeySet = new Set();
for (const item of other) {
const value = key ? key(item) : item;
otherKeySet.add(value);
}
return this.filter(e => {
const ownKey = key ? key(e) : e;
return !otherKeySet.has(ownKey);
});
}
}
function toString(item) {
if (typeof item === 'string') {
return item;
}
if (typeof item === 'undefined') {
return 'undefined';
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
if (typeof item.toString === 'function') {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return item.toString();
}
return Object.prototype.toString.call(item);
}
function isIterable(obj) {
return !!obj && typeof obj[Symbol.iterator] === 'function';
}
/**
* An empty stream of any type.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const EMPTY_STREAM = new StreamImpl(() => undefined, () => DONE_RESULT);
/**
* Use this `IteratorResult` when implementing a `StreamImpl` to indicate that there are no more elements in the stream.
*/
export const DONE_RESULT = Object.freeze({ done: true, value: undefined });
/**
* Create a stream from one or more iterables or array-likes.
*/
export function stream(...collections) {
if (collections.length === 1) {
const collection = collections[0];
if (collection instanceof StreamImpl) {
return collection;
}
if (isIterable(collection)) {
return new StreamImpl(() => collection[Symbol.iterator](), (iterator) => iterator.next());
}
if (typeof collection.length === 'number') {
return new StreamImpl(() => ({ index: 0 }), (state) => {
if (state.index < collection.length) {
return { done: false, value: collection[state.index++] };
}
else {
return DONE_RESULT;
}
});
}
}
if (collections.length > 1) {
return new StreamImpl(() => ({ collIndex: 0, arrIndex: 0 }), (state) => {
do {
if (state.iterator) {
const next = state.iterator.next();
if (!next.done) {
return next;
}
state.iterator = undefined;
}
if (state.array) {
if (state.arrIndex < state.array.length) {
return { done: false, value: state.array[state.arrIndex++] };
}
state.array = undefined;
state.arrIndex = 0;
}
if (state.collIndex < collections.length) {
const collection = collections[state.collIndex++];
if (isIterable(collection)) {
state.iterator = collection[Symbol.iterator]();
}
else if (collection && typeof collection.length === 'number') {
state.array = collection;
}
}
} while (state.iterator || state.array || state.collIndex < collections.length);
return DONE_RESULT;
});
}
return EMPTY_STREAM;
}
/**
* The default implementation of `TreeStream` takes a root element and a function that computes the
* children of its argument. Whether the root node included in the stream is controlled with the
* `includeRoot` option, which defaults to `false`.
*/
export class TreeStreamImpl extends StreamImpl {
constructor(root, children, options) {
super(() => ({
iterators: options?.includeRoot ? [[root][Symbol.iterator]()] : [children(root)[Symbol.iterator]()],
pruned: false
}), state => {
if (state.pruned) {
state.iterators.pop();
state.pruned = false;
}
while (state.iterators.length > 0) {
const iterator = state.iterators[state.iterators.length - 1];
const next = iterator.next();
if (next.done) {
state.iterators.pop();
}
else {
state.iterators.push(children(next.value)[Symbol.iterator]());
return next;
}
}
return DONE_RESULT;
});
}
iterator() {
const iterator = {
state: this.startFn(),
next: () => this.nextFn(iterator.state),
prune: () => {
iterator.state.pruned = true;
},
[Symbol.iterator]: () => iterator
};
return iterator;
}
}
/**
* A set of utility functions that reduce a stream to a single value.
*/
export var Reduction;
(function (Reduction) {
/**
* Compute the sum of a number stream.
*/
function sum(stream) {
return stream.reduce((a, b) => a + b, 0);
}
Reduction.sum = sum;
/**
* Compute the product of a number stream.
*/
function product(stream) {
return stream.reduce((a, b) => a * b, 0);
}
Reduction.product = product;
/**
* Compute the minimum of a number stream. Returns `undefined` if the stream is empty.
*/
function min(stream) {
return stream.reduce((a, b) => Math.min(a, b));
}
Reduction.min = min;
/**
* Compute the maximum of a number stream. Returns `undefined` if the stream is empty.
*/
function max(stream) {
return stream.reduce((a, b) => Math.max(a, b));
}
Reduction.max = max;
})(Reduction || (Reduction = {}));
//# sourceMappingURL=stream.js.map
File diff suppressed because one or more lines are too long
+55
View File
@@ -0,0 +1,55 @@
/******************************************************************************
* Copyright 2022 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, Utils } from 'vscode-uri';
export { URI };
export declare namespace UriUtils {
const basename: typeof Utils.basename;
const dirname: typeof Utils.dirname;
const extname: typeof Utils.extname;
const joinPath: typeof Utils.joinPath;
const resolvePath: typeof Utils.resolvePath;
function equals(a?: URI | string, b?: URI | string): boolean;
function relative(from: URI | string, to: URI | string): string;
function normalize(uri: URI | string): string;
function contains(parent: URI | string, child: URI | string): boolean;
}
interface InternalUriTrieNode<T> {
name: string;
children: Map<string, InternalUriTrieNode<T>>;
parent?: InternalUriTrieNode<T>;
element?: T;
}
export interface UriTrieNode<T> {
name: string;
uri: string;
element?: T;
}
/**
* A trie structure for URIs. It allows to insert, delete and find elements by their URI.
* More specifically, it allows to efficiently find all elements that are children of a given URI.
*
* Unlike a regular trie, this implementation uses the name of the URI segments as keys.
*
* @see {@link https://en.wikipedia.org/wiki/Trie}
*/
export declare class UriTrie<T> {
protected readonly root: InternalUriTrieNode<T>;
protected normalizeUri(uri: URI | string): string;
clear(): void;
insert(uri: URI | string, element: T): void;
delete(uri: URI | string): void;
has(uri: URI | string): boolean;
hasNode(uri: URI | string): boolean;
find(uri: URI | string): T | undefined;
findNode(uri: URI | string): UriTrieNode<T> | undefined;
findChildren(uri: URI | string): Array<UriTrieNode<T>>;
all(): T[];
findAll(prefix: URI | string): T[];
protected getNode(uri: string, create: true): InternalUriTrieNode<T>;
protected getNode(uri: string, create: false): InternalUriTrieNode<T> | undefined;
protected collectValues(node: InternalUriTrieNode<T>): T[];
}
//# sourceMappingURL=uri-utils.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"uri-utils.d.ts","sourceRoot":"","sources":["../../src/utils/uri-utils.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAEhF,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAExC,OAAO,EAAE,GAAG,EAAE,CAAC;AAEf,yBAAiB,QAAQ,CAAC;IAEf,MAAM,QAAQ,uBAAiB,CAAC;IAChC,MAAM,OAAO,sBAAgB,CAAC;IAC9B,MAAM,OAAO,sBAAgB,CAAC;IAC9B,MAAM,QAAQ,uBAAiB,CAAC;IAChC,MAAM,WAAW,0BAAoB,CAAC;IAI7C,SAAgB,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,GAAG,MAAM,EAAE,CAAC,CAAC,EAAE,GAAG,GAAG,MAAM,GAAG,OAAO,CAElE;IAED,SAAgB,QAAQ,CAAC,IAAI,EAAE,GAAG,GAAG,MAAM,EAAE,EAAE,EAAE,GAAG,GAAG,MAAM,GAAG,MAAM,CA6BrE;IAED,SAAgB,SAAS,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,GAAG,MAAM,CAEnD;IAED,SAAgB,QAAQ,CAAC,MAAM,EAAE,GAAG,GAAG,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,MAAM,GAAG,OAAO,CAwB3E;CAEJ;AAED,UAAU,mBAAmB,CAAC,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9C,MAAM,CAAC,EAAE,mBAAmB,CAAC,CAAC,CAAC,CAAC;IAEhC,OAAO,CAAC,EAAE,CAAC,CAAC;CACf;AAED,MAAM,WAAW,WAAW,CAAC,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,CAAC,CAAC;CACf;AAED;;;;;;;GAOG;AACH,qBAAa,OAAO,CAAC,CAAC;IAElB,SAAS,CAAC,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAC,CAAC,CAAC,CAAqC;IAEpF,SAAS,CAAC,YAAY,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,GAAG,MAAM;IAIjD,KAAK,IAAI,IAAI;IAIb,MAAM,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,IAAI;IAK3C,MAAM,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,GAAG,IAAI;IAO/B,GAAG,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,GAAG,OAAO;IAI/B,OAAO,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,GAAG,OAAO;IAInC,IAAI,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,GAAG,CAAC,GAAG,SAAS;IAItC,QAAQ,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC,GAAG,SAAS;IAavD,YAAY,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,GAAG,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;IAatD,GAAG,IAAI,CAAC,EAAE;IAIV,OAAO,CAAC,MAAM,EAAE,GAAG,GAAG,MAAM,GAAG,CAAC,EAAE;IAQlC,SAAS,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,GAAG,mBAAmB,CAAC,CAAC,CAAC;IACpE,SAAS,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,GAAG,mBAAmB,CAAC,CAAC,CAAC,GAAG,SAAS;IA2BjF,SAAS,CAAC,aAAa,CAAC,IAAI,EAAE,mBAAmB,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE;CAW7D"}
+188
View File
@@ -0,0 +1,188 @@
/******************************************************************************
* Copyright 2022 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, Utils } from 'vscode-uri';
export { URI };
export var UriUtils;
(function (UriUtils) {
UriUtils.basename = Utils.basename;
UriUtils.dirname = Utils.dirname;
UriUtils.extname = Utils.extname;
UriUtils.joinPath = Utils.joinPath;
UriUtils.resolvePath = Utils.resolvePath;
const isWindows = typeof process === 'object' && process?.platform === 'win32';
function equals(a, b) {
return a?.toString() === b?.toString();
}
UriUtils.equals = equals;
function relative(from, to) {
const fromPath = typeof from === 'string' ? URI.parse(from).path : from.path;
const toPath = typeof to === 'string' ? URI.parse(to).path : to.path;
const fromParts = fromPath.split('/').filter(e => e.length > 0);
const toParts = toPath.split('/').filter(e => e.length > 0);
if (isWindows) {
const upperCaseDriveLetter = /^[A-Z]:$/;
if (fromParts[0] && upperCaseDriveLetter.test(fromParts[0])) {
fromParts[0] = fromParts[0].toLowerCase();
}
if (toParts[0] && upperCaseDriveLetter.test(toParts[0])) {
toParts[0] = toParts[0].toLowerCase();
}
if (fromParts[0] !== toParts[0]) {
// in case of different drive letters, we cannot compute a relative path, so...
return toPath.substring(1); // fall back to full 'to' path, drop the leading '/', keep everything else as is for good comparability
}
}
let i = 0;
for (; i < fromParts.length; i++) {
if (fromParts[i] !== toParts[i]) {
break;
}
}
const backPart = '../'.repeat(fromParts.length - i);
const toPart = toParts.slice(i).join('/');
return backPart + toPart;
}
UriUtils.relative = relative;
function normalize(uri) {
return URI.parse(uri.toString()).toString();
}
UriUtils.normalize = normalize;
function contains(parent, child) {
let parentPath = typeof parent === 'string' ? parent : parent.path;
let childPath = typeof child === 'string' ? child : child.path;
// Trim trailing slashes
if (childPath.charAt(childPath.length - 1) === '/') {
childPath = childPath.slice(0, -1);
}
if (parentPath.charAt(parentPath.length - 1) === '/') {
parentPath = parentPath.slice(0, -1);
}
// If the paths are equal, simply return true
if (childPath === parentPath) {
return true;
}
// If the child path is shorter than the parent path, it can't be a child
if (childPath.length < parentPath.length) {
return false;
}
// If the path does not feature a slash after the parent path, it can't be a child
if (childPath.charAt(parentPath.length) !== '/') {
return false;
}
// Check if the child path starts with the parent path
return childPath.startsWith(parentPath);
}
UriUtils.contains = contains;
})(UriUtils || (UriUtils = {}));
/**
* A trie structure for URIs. It allows to insert, delete and find elements by their URI.
* More specifically, it allows to efficiently find all elements that are children of a given URI.
*
* Unlike a regular trie, this implementation uses the name of the URI segments as keys.
*
* @see {@link https://en.wikipedia.org/wiki/Trie}
*/
export class UriTrie {
constructor() {
this.root = { name: '', children: new Map() };
}
normalizeUri(uri) {
return UriUtils.normalize(uri);
}
clear() {
this.root.children.clear();
}
insert(uri, element) {
const node = this.getNode(this.normalizeUri(uri), true);
node.element = element;
}
delete(uri) {
const nodeToDelete = this.getNode(this.normalizeUri(uri), false);
if (nodeToDelete?.parent) {
nodeToDelete.parent.children.delete(nodeToDelete.name);
}
}
has(uri) {
return this.getNode(this.normalizeUri(uri), false)?.element !== undefined;
}
hasNode(uri) {
return this.getNode(this.normalizeUri(uri), false) !== undefined;
}
find(uri) {
return this.getNode(this.normalizeUri(uri), false)?.element;
}
findNode(uri) {
const uriString = this.normalizeUri(uri);
const node = this.getNode(uriString, false);
if (!node) {
return undefined;
}
return {
name: node.name,
uri: UriUtils.joinPath(URI.parse(uriString), node.name).toString(),
element: node.element
};
}
findChildren(uri) {
const uriString = this.normalizeUri(uri);
const node = this.getNode(uriString, false);
if (!node) {
return [];
}
return Array.from(node.children.values()).map(child => ({
name: child.name,
uri: UriUtils.joinPath(URI.parse(uriString), child.name).toString(),
element: child.element
}));
}
all() {
return this.collectValues(this.root);
}
findAll(prefix) {
const node = this.getNode(UriUtils.normalize(prefix), false);
if (!node) {
return [];
}
return this.collectValues(node);
}
getNode(uri, create) {
const parts = uri.split('/');
if (uri.charAt(uri.length - 1) === '/') {
// Remove the last part if the URI ends with a slash
parts.pop();
}
let current = this.root;
for (const part of parts) {
let child = current.children.get(part);
if (!child) {
if (create) {
child = {
name: part,
children: new Map(),
parent: current
};
current.children.set(part, child);
}
else {
return undefined;
}
}
current = child;
}
return current;
}
collectValues(node) {
const result = [];
if (node.element) {
result.push(node.element);
}
for (const child of node.children.values()) {
result.push(...this.collectValues(child));
}
return result;
}
}
//# sourceMappingURL=uri-utils.js.map
File diff suppressed because one or more lines are too long