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
+38
View File
@@ -0,0 +1,38 @@
/******************************************************************************
* 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 { Module } from './dependency-injection.js';
import type { LangiumDefaultCoreServices, LangiumDefaultSharedCoreServices, LangiumCoreServices, LangiumSharedCoreServices } from './services.js';
import type { FileSystemProvider } from './workspace/file-system-provider.js';
/**
* Context required for creating the default language-specific dependency injection module.
*/
export interface DefaultCoreModuleContext {
shared: LangiumSharedCoreServices;
}
/**
* Creates a dependency injection module configuring the default core services.
* This is a set of services that are dedicated to a specific language.
*/
export declare function createDefaultCoreModule(context: DefaultCoreModuleContext): Module<LangiumCoreServices, LangiumDefaultCoreServices>;
/**
* Context required for creating the default shared dependency injection module.
*/
export interface DefaultSharedCoreModuleContext {
/**
* Factory function to create a {@link FileSystemProvider}.
*
* Langium exposes an `EmptyFileSystem` and `NodeFileSystem`, exported through `langium/node`.
* When running Langium as part of a vscode language server or a Node.js app, using the `NodeFileSystem` is recommended,
* the `EmptyFileSystem` in every other use case.
*/
fileSystemProvider: (services: LangiumSharedCoreServices) => FileSystemProvider;
}
/**
* Creates a dependency injection module configuring the default shared core services.
* This is the set of services that are shared between multiple languages.
*/
export declare function createDefaultSharedCoreModule(context: DefaultSharedCoreModuleContext): Module<LangiumSharedCoreServices, LangiumDefaultSharedCoreServices>;
//# sourceMappingURL=default-module.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"default-module.d.ts","sourceRoot":"","sources":["../src/default-module.ts"],"names":[],"mappings":"AAAA;;;;+EAI+E;AAE/E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,2BAA2B,CAAC;AACxD,OAAO,KAAK,EAAE,0BAA0B,EAAE,gCAAgC,EAAE,mBAAmB,EAAE,yBAAyB,EAAE,MAAM,eAAe,CAAC;AAClJ,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qCAAqC,CAAC;AA8B9E;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACrC,MAAM,EAAE,yBAAyB,CAAC;CACrC;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,wBAAwB,GAAG,MAAM,CAAC,mBAAmB,EAAE,0BAA0B,CAAC,CAuClI;AAED;;GAEG;AACH,MAAM,WAAW,8BAA8B;IAC3C;;;;;;OAMG;IACH,kBAAkB,EAAE,CAAC,QAAQ,EAAE,yBAAyB,KAAK,kBAAkB,CAAC;CACnF;AAED;;;GAGG;AACH,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,8BAA8B,GAAG,MAAM,CAAC,yBAAyB,EAAE,gCAAgC,CAAC,CAe1J"}
+98
View File
@@ -0,0 +1,98 @@
/******************************************************************************
* 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 { createGrammarConfig } from './languages/grammar-config.js';
import { createCompletionParser } from './parser/completion-parser-builder.js';
import { createLangiumParser } from './parser/langium-parser-builder.js';
import { DefaultTokenBuilder } from './parser/token-builder.js';
import { DefaultValueConverter } from './parser/value-converter.js';
import { DefaultLinker } from './references/linker.js';
import { DefaultNameProvider } from './references/name-provider.js';
import { DefaultReferences } from './references/references.js';
import { DefaultScopeComputation } from './references/scope-computation.js';
import { DefaultScopeProvider } from './references/scope-provider.js';
import { DefaultJsonSerializer } from './serializer/json-serializer.js';
import { DefaultServiceRegistry } from './service-registry.js';
import { DefaultDocumentValidator } from './validation/document-validator.js';
import { ValidationRegistry } from './validation/validation-registry.js';
import { DefaultAstNodeDescriptionProvider, DefaultReferenceDescriptionProvider } from './workspace/ast-descriptions.js';
import { DefaultAstNodeLocator } from './workspace/ast-node-locator.js';
import { DefaultConfigurationProvider } from './workspace/configuration.js';
import { DefaultDocumentBuilder } from './workspace/document-builder.js';
import { DefaultLangiumDocumentFactory, DefaultLangiumDocuments } from './workspace/documents.js';
import { DefaultIndexManager } from './workspace/index-manager.js';
import { DefaultWorkspaceManager } from './workspace/workspace-manager.js';
import { DefaultLexer, DefaultLexerErrorMessageProvider } from './parser/lexer.js';
import { JSDocDocumentationProvider } from './documentation/documentation-provider.js';
import { DefaultCommentProvider } from './documentation/comment-provider.js';
import { LangiumParserErrorMessageProvider } from './parser/langium-parser.js';
import { DefaultAsyncParser } from './parser/async-parser.js';
import { DefaultWorkspaceLock } from './workspace/workspace-lock.js';
import { DefaultHydrator } from './serializer/hydrator.js';
/**
* Creates a dependency injection module configuring the default core services.
* This is a set of services that are dedicated to a specific language.
*/
export function createDefaultCoreModule(context) {
return {
documentation: {
CommentProvider: (services) => new DefaultCommentProvider(services),
DocumentationProvider: (services) => new JSDocDocumentationProvider(services)
},
parser: {
AsyncParser: (services) => new DefaultAsyncParser(services),
GrammarConfig: (services) => createGrammarConfig(services),
LangiumParser: (services) => createLangiumParser(services),
CompletionParser: (services) => createCompletionParser(services),
ValueConverter: () => new DefaultValueConverter(),
TokenBuilder: () => new DefaultTokenBuilder(),
Lexer: (services) => new DefaultLexer(services),
ParserErrorMessageProvider: () => new LangiumParserErrorMessageProvider(),
LexerErrorMessageProvider: () => new DefaultLexerErrorMessageProvider()
},
workspace: {
AstNodeLocator: () => new DefaultAstNodeLocator(),
AstNodeDescriptionProvider: (services) => new DefaultAstNodeDescriptionProvider(services),
ReferenceDescriptionProvider: (services) => new DefaultReferenceDescriptionProvider(services)
},
references: {
Linker: (services) => new DefaultLinker(services),
NameProvider: () => new DefaultNameProvider(),
ScopeProvider: (services) => new DefaultScopeProvider(services),
ScopeComputation: (services) => new DefaultScopeComputation(services),
References: (services) => new DefaultReferences(services)
},
serializer: {
Hydrator: (services) => new DefaultHydrator(services),
JsonSerializer: (services) => new DefaultJsonSerializer(services)
},
validation: {
DocumentValidator: (services) => new DefaultDocumentValidator(services),
ValidationRegistry: (services) => new ValidationRegistry(services)
},
shared: () => context.shared
};
}
/**
* Creates a dependency injection module configuring the default shared core services.
* This is the set of services that are shared between multiple languages.
*/
export function createDefaultSharedCoreModule(context) {
return {
ServiceRegistry: (services) => new DefaultServiceRegistry(services),
workspace: {
LangiumDocuments: (services) => new DefaultLangiumDocuments(services),
LangiumDocumentFactory: (services) => new DefaultLangiumDocumentFactory(services),
DocumentBuilder: (services) => new DefaultDocumentBuilder(services),
IndexManager: (services) => new DefaultIndexManager(services),
WorkspaceManager: (services) => new DefaultWorkspaceManager(services),
FileSystemProvider: (services) => context.fileSystemProvider(services),
WorkspaceLock: () => new DefaultWorkspaceLock(),
ConfigurationProvider: (services) => new DefaultConfigurationProvider(services),
},
profilers: {}
};
}
//# sourceMappingURL=default-module.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"default-module.js","sourceRoot":"","sources":["../src/default-module.ts"],"names":[],"mappings":"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"}
+59
View File
@@ -0,0 +1,59 @@
/******************************************************************************
* 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 `Module<I>` is a description of possibly grouped service factories.
*
* Given a type I = { group: { service: A } },
* Module<I> := { group: { service: (injector: I) => A } }
*
* Making `I` available during the creation of `I` allows us to create cyclic
* dependencies.
*/
export type Module<I, T = I> = {
[K in keyof T]: Module<I, T[K]> | ((injector: I) => T[K]);
};
export declare namespace Module {
/**
* Merges two dependency injection modules into a new (third) one that is returned.
* At that `m1` and `m2` stay unchanged. Therefore, `m1` is deep-copied first,
* and m2 is merged onto the copy afterwards.
*
* Note that the leaf values of `m1` and `m2`, i.e. the service constructor functions,
* cannot be copied generically, since they are functions. They are shared by the source and merged modules.
*
* @returns the merged module being a deep copy of `m1` with `m2` merged onto it.
*/
const merge: <M1, M2, R extends M1 & M2>(m1: Module<R, M1>, m2: Module<R, M2>) => Module<R, M1 & M2>;
}
/**
* Given a set of modules, the inject function returns a lazily evaluated injector
* that injects dependencies into the requested service when it is requested the
* first time. Subsequent requests will return the same service.
*
* In the case of cyclic dependencies, an Error will be thrown. This can be fixed
* by injecting a provider `() => T` instead of a `T`.
*
* Please note that the arguments may be objects or arrays. However, the result will
* be an object. Using it with for..of will have no effect.
*
* @param module1 first Module
* @param module2 (optional) second Module
* @param module3 (optional) third Module
* @param module4 (optional) fourth Module
* @param module5 (optional) fifth Module
* @param module6 (optional) sixth Module
* @param module7 (optional) seventh Module
* @param module8 (optional) eighth Module
* @param module9 (optional) ninth Module
* @returns a new object of type I
*/
export declare function inject<I1, I2, I3, I4, I5, I6, I7, I8, I9, I extends I1 & I2 & I3 & I4 & I5 & I6 & I7 & I8 & I9>(module1: Module<I, I1>, module2?: Module<I, I2>, module3?: Module<I, I3>, module4?: Module<I, I4>, module5?: Module<I, I5>, module6?: Module<I, I6>, module7?: Module<I, I7>, module8?: Module<I, I8>, module9?: Module<I, I9>): I;
/**
* Eagerly load all services in the given dependency injection container. This is sometimes
* necessary because services can register event listeners in their constructors.
*/
export declare function eagerLoad<T>(item: T): T;
//# sourceMappingURL=dependency-injection.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"dependency-injection.d.ts","sourceRoot":"","sources":["../src/dependency-injection.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAIhF;;;;;;;;GAQG;AACH,MAAM,MAAM,MAAM,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI;KAC1B,CAAC,IAAI,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;CAC5D,CAAA;AAED,yBAAiB,MAAM,CAAC;IACpB;;;;;;;;;OASG;IACI,MAAM,KAAK,GAAI,EAAE,EAAE,EAAE,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE,EAAE,IAAI,MAAM,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,IAAI,MAAM,CAAC,CAAC,EAAE,EAAE,CAAC,KAAoC,MAAM,CAAC,CAAC,EAAE,EAAE,GAAG,EAAE,CAAE,CAAC;CAChJ;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,MAAM,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,EAC3G,OAAO,EAAE,MAAM,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,CAAC,EAAE,EAAE,CAAC,GAC/N,CAAC,CAGH;AAID;;;GAGG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,CAOvC"}
+164
View File
@@ -0,0 +1,164 @@
/******************************************************************************
* 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 Module;
(function (Module) {
/**
* Merges two dependency injection modules into a new (third) one that is returned.
* At that `m1` and `m2` stay unchanged. Therefore, `m1` is deep-copied first,
* and m2 is merged onto the copy afterwards.
*
* Note that the leaf values of `m1` and `m2`, i.e. the service constructor functions,
* cannot be copied generically, since they are functions. They are shared by the source and merged modules.
*
* @returns the merged module being a deep copy of `m1` with `m2` merged onto it.
*/
Module.merge = (m1, m2) => _merge(_merge({}, m1), m2);
})(Module || (Module = {}));
/**
* Given a set of modules, the inject function returns a lazily evaluated injector
* that injects dependencies into the requested service when it is requested the
* first time. Subsequent requests will return the same service.
*
* In the case of cyclic dependencies, an Error will be thrown. This can be fixed
* by injecting a provider `() => T` instead of a `T`.
*
* Please note that the arguments may be objects or arrays. However, the result will
* be an object. Using it with for..of will have no effect.
*
* @param module1 first Module
* @param module2 (optional) second Module
* @param module3 (optional) third Module
* @param module4 (optional) fourth Module
* @param module5 (optional) fifth Module
* @param module6 (optional) sixth Module
* @param module7 (optional) seventh Module
* @param module8 (optional) eighth Module
* @param module9 (optional) ninth Module
* @returns a new object of type I
*/
export function inject(module1, module2, module3, module4, module5, module6, module7, module8, module9) {
const module = [module1, module2, module3, module4, module5, module6, module7, module8, module9].reduce(_merge, {});
return _inject(module);
}
const isProxy = Symbol('isProxy');
/**
* Eagerly load all services in the given dependency injection container. This is sometimes
* necessary because services can register event listeners in their constructors.
*/
export function eagerLoad(item) {
if (item && item[isProxy]) {
for (const value of Object.values(item)) {
eagerLoad(value);
}
}
return item;
}
/**
* Helper function that returns an injector by creating a proxy.
* Invariant: injector is of type I. If injector is undefined, then T = I.
*/
function _inject(module, injector) {
const proxy = new Proxy({}, {
deleteProperty: () => false,
set: () => {
throw new Error('Cannot set property on injected service container');
},
get: (obj, prop) => {
if (prop === isProxy) {
return true;
}
else {
return _resolve(obj, prop, module, injector || proxy);
}
},
getOwnPropertyDescriptor: (obj, prop) => (_resolve(obj, prop, module, injector || proxy), Object.getOwnPropertyDescriptor(obj, prop)), // used by for..in
has: (_, prop) => prop in module, // used by ..in..
ownKeys: () => [...Object.getOwnPropertyNames(module)] // used by for..in
});
return proxy;
}
/**
* Internally used to tag a requested dependency, directly before calling the factory.
* This allows us to find cycles during instance creation.
*/
const __requested__ = Symbol();
/**
* Returns the value `obj[prop]`. If the value does not exist, yet, it is resolved from
* the module description. The result of service factories is cached. Groups are
* recursively proxied.
*
* @param obj an object holding all group proxies and services
* @param prop the key of a value within obj
* @param module an object containing groups and service factories
* @param injector the first level proxy that provides access to all values
* @returns the requested value `obj[prop]`
* @throws Error if a dependency cycle is detected
*/
function _resolve(obj, prop, module, injector) {
if (prop in obj) {
if (obj[prop] instanceof Error) {
throw new Error('Construction failure. Please make sure that your dependencies are constructable. Cause: ' + obj[prop]);
}
if (obj[prop] === __requested__) {
throw new Error('Cycle detected. Please make "' + String(prop) + '" lazy. Visit https://langium.org/docs/reference/configuration-services/#resolving-cyclic-dependencies');
}
return obj[prop];
}
else if (prop in module) {
const value = module[prop];
obj[prop] = __requested__;
try {
obj[prop] = (typeof value === 'function') ? value(injector) : _inject(value, injector);
}
catch (error) {
obj[prop] = error instanceof Error ? error : undefined;
throw error;
}
return obj[prop];
}
else {
return undefined;
}
}
/**
* Performs a deep-merge of two modules by writing source entries into the target module.
*
* @param target the module which is written
* @param source the module which is read
* @returns the target module
*/
function _merge(target, source) {
if (source) {
for (const [key, sourceValue] of Object.entries(source)) {
if (sourceValue !== undefined && sourceValue !== null) {
if (typeof sourceValue === 'object') {
const targetValue = target[key];
if (typeof targetValue === 'object' && targetValue !== null) {
// in case both values are real (non-null) objects merge them recursively
target[key] = _merge(targetValue, sourceValue);
}
else {
// in case 'target[key]' is not a non-null object
// we overwrite any existing value with a deep copy of 'sourceValue'
// by recursively calling this function with a new 'target' object to be populated
// that is assigned to 'target[key]' afterwards
target[key] = _merge({}, sourceValue);
}
}
else {
// in case 'sourceValue' is defined and assigned (non-null) but not an object
// we assume it to be a service constructor function according to the Module<I> type definition
target[key] = sourceValue;
// note the following for such service constructor functions:
// 'target[key]' will now reference the same function object being referenced by 'source[key]'.
// This is accepted here, since function objects cannot be safely copied in general.
}
}
}
}
return target;
}
//# sourceMappingURL=dependency-injection.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"dependency-injection.js","sourceRoot":"","sources":["../src/dependency-injection.ts"],"names":[],"mappings":"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"}
+25
View File
@@ -0,0 +1,25 @@
/******************************************************************************
* 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 { GrammarConfig } from '../languages/grammar-config.js';
import type { LangiumCoreServices } from '../services.js';
import type { AstNode } from '../syntax-tree.js';
/**
* Provides comments for AST nodes.
*/
export interface CommentProvider {
/**
* Returns the comment associated with the specified AST node.
* @param node The AST node to get the comment for.
* @returns The comment associated with the specified AST node or `undefined` if there is no comment.
*/
getComment(node: AstNode): string | undefined;
}
export declare class DefaultCommentProvider implements CommentProvider {
protected readonly grammarConfig: () => GrammarConfig;
constructor(services: LangiumCoreServices);
getComment(node: AstNode): string | undefined;
}
//# sourceMappingURL=comment-provider.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"comment-provider.d.ts","sourceRoot":"","sources":["../../src/documentation/comment-provider.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAEhF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAEpE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAGjD;;GAEG;AACH,MAAM,WAAW,eAAe;IAC5B;;;;OAIG;IACH,UAAU,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAAC;CACjD;AAED,qBAAa,sBAAuB,YAAW,eAAe;IAC1D,SAAS,CAAC,QAAQ,CAAC,aAAa,EAAE,MAAM,aAAa,CAAC;gBAC1C,QAAQ,EAAE,mBAAmB;IAGzC,UAAU,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS;CAMhD"}
+19
View File
@@ -0,0 +1,19 @@
/******************************************************************************
* 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 { isAstNodeWithComment } from '../serializer/json-serializer.js';
import { findCommentNode } from '../utils/cst-utils.js';
export class DefaultCommentProvider {
constructor(services) {
this.grammarConfig = () => services.parser.GrammarConfig;
}
getComment(node) {
if (isAstNodeWithComment(node)) {
return node.$comment;
}
return findCommentNode(node.$cstNode, this.grammarConfig().multilineCommentRules)?.text;
}
}
//# sourceMappingURL=comment-provider.js.map
@@ -0,0 +1 @@
{"version":3,"file":"comment-provider.js","sourceRoot":"","sources":["../../src/documentation/comment-provider.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAGhF,OAAO,EAAE,oBAAoB,EAAE,MAAM,kCAAkC,CAAC;AAGxE,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAcxD,MAAM,OAAO,sBAAsB;IAE/B,YAAY,QAA6B;QACrC,IAAI,CAAC,aAAa,GAAG,GAAG,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,aAAa,CAAC;IAC7D,CAAC;IACD,UAAU,CAAC,IAAa;QACpB,IAAG,oBAAoB,CAAC,IAAI,CAAC,EAAE,CAAC;YAC5B,OAAO,IAAI,CAAC,QAAQ,CAAC;QACzB,CAAC;QACD,OAAO,eAAe,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,aAAa,EAAE,CAAC,qBAAqB,CAAC,EAAE,IAAI,CAAC;IAC5F,CAAC;CACJ"}
@@ -0,0 +1,32 @@
/******************************************************************************
* 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 { LangiumCoreServices } from '../services.js';
import type { AstNode, AstNodeDescription } from '../syntax-tree.js';
import type { IndexManager } from '../workspace/index-manager.js';
import type { CommentProvider } from './comment-provider.js';
import type { JSDocTag } from './jsdoc.js';
/**
* Provides documentation for AST nodes.
*/
export interface DocumentationProvider {
/**
* Returns a markdown documentation string for the specified AST node.
*
* The default implementation `JSDocDocumentationProvider` will inspect the comment associated with the specified node.
*/
getDocumentation(node: AstNode): string | undefined;
}
export declare class JSDocDocumentationProvider implements DocumentationProvider {
protected readonly indexManager: IndexManager;
protected readonly commentProvider: CommentProvider;
constructor(services: LangiumCoreServices);
getDocumentation(node: AstNode): string | undefined;
protected documentationLinkRenderer(node: AstNode, name: string, display: string): string | undefined;
protected documentationTagRenderer(_node: AstNode, _tag: JSDocTag): string | undefined;
protected findNameInLocalSymbols(node: AstNode, name: string): AstNodeDescription | undefined;
protected findNameInGlobalScope(node: AstNode, name: string): AstNodeDescription | undefined;
}
//# sourceMappingURL=documentation-provider.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"documentation-provider.d.ts","sourceRoot":"","sources":["../../src/documentation/documentation-provider.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAEhF,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,KAAK,EAAE,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACrE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAClE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAI3C;;GAEG;AACH,MAAM,WAAW,qBAAqB;IAClC;;;;OAIG;IACH,gBAAgB,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAAC;CACvD;AAED,qBAAa,0BAA2B,YAAW,qBAAqB;IAEpE,SAAS,CAAC,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;IAC9C,SAAS,CAAC,QAAQ,CAAC,eAAe,EAAE,eAAe,CAAC;gBAExC,QAAQ,EAAE,mBAAmB;IAKzC,gBAAgB,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS;IAgBnD,SAAS,CAAC,yBAAyB,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAYrG,SAAS,CAAC,wBAAwB,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,GAAG,MAAM,GAAG,SAAS;IAKtF,SAAS,CAAC,sBAAsB,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS;IAmB7F,SAAS,CAAC,qBAAqB,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS;CAI/F"}
@@ -0,0 +1,66 @@
/******************************************************************************
* 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 { getDocument } from '../utils/ast-utils.js';
import { isJSDoc, parseJSDoc } from './jsdoc.js';
export class JSDocDocumentationProvider {
constructor(services) {
this.indexManager = services.shared.workspace.IndexManager;
this.commentProvider = services.documentation.CommentProvider;
}
getDocumentation(node) {
const comment = this.commentProvider.getComment(node);
if (comment && isJSDoc(comment)) {
const parsedJSDoc = parseJSDoc(comment);
return parsedJSDoc.toMarkdown({
renderLink: (link, display) => {
return this.documentationLinkRenderer(node, link, display);
},
renderTag: (tag) => {
return this.documentationTagRenderer(node, tag);
}
});
}
return undefined;
}
documentationLinkRenderer(node, name, display) {
const description = this.findNameInLocalSymbols(node, name) ?? this.findNameInGlobalScope(node, name);
if (description && description.nameSegment) {
const line = description.nameSegment.range.start.line + 1;
const character = description.nameSegment.range.start.character + 1;
const uri = description.documentUri.with({ fragment: `L${line},${character}` });
return `[${display}](${uri.toString()})`;
}
else {
return undefined;
}
}
documentationTagRenderer(_node, _tag) {
// Fall back to the default tag rendering
return undefined;
}
findNameInLocalSymbols(node, name) {
const document = getDocument(node);
const precomputed = document.localSymbols;
if (!precomputed) {
return undefined;
}
let currentNode = node;
do {
const allDescriptions = precomputed.getStream(currentNode);
const description = allDescriptions.find(e => e.name === name);
if (description) {
return description;
}
currentNode = currentNode.$container;
} while (currentNode);
return undefined;
}
findNameInGlobalScope(node, name) {
const description = this.indexManager.allElements().find(e => e.name === name);
return description;
}
}
//# sourceMappingURL=documentation-provider.js.map
@@ -0,0 +1 @@
{"version":3,"file":"documentation-provider.js","sourceRoot":"","sources":["../../src/documentation/documentation-provider.ts"],"names":[],"mappings":"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"}
+9
View File
@@ -0,0 +1,9 @@
/******************************************************************************
* 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 './comment-provider.js';
export * from './documentation-provider.js';
export * from './jsdoc.js';
//# sourceMappingURL=index.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/documentation/index.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAEhF,cAAc,uBAAuB,CAAC;AACtC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,YAAY,CAAC"}
+9
View File
@@ -0,0 +1,9 @@
/******************************************************************************
* 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 './comment-provider.js';
export * from './documentation-provider.js';
export * from './jsdoc.js';
//# sourceMappingURL=index.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/documentation/index.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAEhF,cAAc,uBAAuB,CAAC;AACtC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,YAAY,CAAC"}
+93
View File
@@ -0,0 +1,93 @@
/******************************************************************************
* 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 { Position, Range } from 'vscode-languageserver-types';
import type { CstNode } from '../syntax-tree.js';
export interface JSDocComment extends JSDocValue {
readonly elements: JSDocElement[];
getTag(name: string): JSDocTag | undefined;
getTags(name: string): JSDocTag[];
}
export type JSDocElement = JSDocParagraph | JSDocTag;
export type JSDocInline = JSDocTag | JSDocLine;
export interface JSDocValue {
/**
* Represents the range that this JSDoc element occupies.
* If the JSDoc was parsed from a `CstNode`, the range will represent the location in the source document.
*/
readonly range: Range;
/**
* Renders this JSDoc element to a plain text representation.
*/
toString(): string;
/**
* Renders this JSDoc element to a markdown representation.
*
* @param options Rendering options to customize the markdown result.
*/
toMarkdown(options?: JSDocRenderOptions): string;
}
export interface JSDocParagraph extends JSDocValue {
readonly inlines: JSDocInline[];
}
export interface JSDocLine extends JSDocValue {
readonly text: string;
}
export interface JSDocTag extends JSDocValue {
readonly name: string;
readonly content: JSDocParagraph;
readonly inline: boolean;
}
export interface JSDocParseOptions {
/**
* The start symbol of your comment format. Defaults to `/**`.
*/
readonly start?: RegExp | string;
/**
* The symbol that start a line of your comment format. Defaults to `*`.
*/
readonly line?: RegExp | string;
/**
* The end symbol of your comment format. Defaults to `*\/`.
*/
readonly end?: RegExp | string;
}
export interface JSDocRenderOptions {
/**
* Determines the style for rendering tags. Defaults to `italic`.
*/
tag?: 'plain' | 'italic' | 'bold' | 'bold-italic';
/**
* Determines the default for rendering `@link` tags. Defaults to `plain`.
*/
link?: 'code' | 'plain';
/**
* Custom tag rendering function.
* Return a markdown formatted tag or `undefined` to fall back to the default rendering.
*/
renderTag?(tag: JSDocTag): string | undefined;
/**
* Custom link rendering function. Accepts a link target and a display value for the link.
* Return a markdown formatted link with the format `[$display]($link)` or `undefined` if the link is not a valid target.
*/
renderLink?(link: string, display: string): string | undefined;
}
/**
* Parses a JSDoc from a `CstNode` containing a comment.
*
* @param node A `CstNode` from a parsed Langium document.
* @param options Parsing options specialized to your language. See {@link JSDocParseOptions}.
*/
export declare function parseJSDoc(node: CstNode, options?: JSDocParseOptions): JSDocComment;
/**
* Parses a JSDoc from a string comment.
*
* @param content A string containing the source of the JSDoc comment.
* @param start The start position the comment occupies in the source document.
* @param options Parsing options specialized to your language. See {@link JSDocParseOptions}.
*/
export declare function parseJSDoc(content: string, start?: Position, options?: JSDocParseOptions): JSDocComment;
export declare function isJSDoc(node: CstNode | string, options?: JSDocParseOptions): boolean;
//# sourceMappingURL=jsdoc.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"jsdoc.d.ts","sourceRoot":"","sources":["../../src/documentation/jsdoc.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAEhF,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAIjD,MAAM,WAAW,YAAa,SAAQ,UAAU;IAC5C,QAAQ,CAAC,QAAQ,EAAE,YAAY,EAAE,CAAA;IACjC,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAA;IAC1C,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,EAAE,CAAA;CACpC;AAED,MAAM,MAAM,YAAY,GAAG,cAAc,GAAG,QAAQ,CAAC;AAErD,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,SAAS,CAAC;AAE/C,MAAM,WAAW,UAAU;IACvB;;;OAGG;IACH,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAA;IACrB;;OAEG;IACH,QAAQ,IAAI,MAAM,CAAA;IAClB;;;;OAIG;IACH,UAAU,CAAC,OAAO,CAAC,EAAE,kBAAkB,GAAG,MAAM,CAAA;CACnD;AAED,MAAM,WAAW,cAAe,SAAQ,UAAU;IAC9C,QAAQ,CAAC,OAAO,EAAE,WAAW,EAAE,CAAA;CAClC;AAED,MAAM,WAAW,SAAU,SAAQ,UAAU;IACzC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACxB;AAED,MAAM,WAAW,QAAS,SAAQ,UAAU;IACxC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAA;IAChC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAA;CAC3B;AAED,MAAM,WAAW,iBAAiB;IAC9B;;OAEG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IAChC;;OAEG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IAC/B;;OAEG;IACH,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;CACjC;AAED,MAAM,WAAW,kBAAkB;IAC/B;;OAEG;IACH,GAAG,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,MAAM,GAAG,aAAa,CAAA;IACjD;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;IACvB;;;OAGG;IACH,SAAS,CAAC,CAAC,GAAG,EAAE,QAAQ,GAAG,MAAM,GAAG,SAAS,CAAA;IAC7C;;;OAGG;IACH,UAAU,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;CACjE;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,iBAAiB,GAAG,YAAY,CAAC;AACrF;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,QAAQ,EAAE,OAAO,CAAC,EAAE,iBAAiB,GAAG,YAAY,CAAC;AA+BzG,wBAAgB,OAAO,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,EAAE,OAAO,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAapF"}
+504
View File
@@ -0,0 +1,504 @@
/******************************************************************************
* 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 { Position, Range } from 'vscode-languageserver-types';
import { NEWLINE_REGEXP, escapeRegExp } from '../utils/regexp-utils.js';
import { URI } from '../utils/uri-utils.js';
export function parseJSDoc(node, start, options) {
let opts;
let position;
if (typeof node === 'string') {
position = start;
opts = options;
}
else {
position = node.range.start;
opts = start;
}
if (!position) {
position = Position.create(0, 0);
}
const lines = getLines(node);
const normalizedOptions = normalizeOptions(opts);
const tokens = tokenize({
lines,
position,
options: normalizedOptions
});
return parseJSDocComment({
index: 0,
tokens,
position
});
}
export function isJSDoc(node, options) {
const normalizedOptions = normalizeOptions(options);
const lines = getLines(node);
if (lines.length === 0) {
return false;
}
const first = lines[0];
const last = lines[lines.length - 1];
const firstRegex = normalizedOptions.start;
const lastRegex = normalizedOptions.end;
return Boolean(firstRegex?.exec(first)) && Boolean(lastRegex?.exec(last));
}
function getLines(node) {
let content = '';
if (typeof node === 'string') {
content = node;
}
else {
content = node.text;
}
const lines = content.split(NEWLINE_REGEXP);
return lines;
}
const tagRegex = /\s*(@([\p{L}][\p{L}\p{N}]*)?)/uy;
const inlineTagRegex = /\{(@[\p{L}][\p{L}\p{N}]*)(\s*)([^\r\n}]+)?\}/gu;
function tokenize(context) {
const tokens = [];
let currentLine = context.position.line;
let currentCharacter = context.position.character;
for (let i = 0; i < context.lines.length; i++) {
const first = i === 0;
const last = i === context.lines.length - 1;
let line = context.lines[i];
let index = 0;
if (first && context.options.start) {
const match = context.options.start?.exec(line);
if (match) {
index = match.index + match[0].length;
}
}
else {
const match = context.options.line?.exec(line);
if (match) {
index = match.index + match[0].length;
}
}
if (last) {
const match = context.options.end?.exec(line);
if (match) {
line = line.substring(0, match.index);
}
}
line = line.substring(0, lastCharacter(line));
const whitespaceEnd = skipWhitespace(line, index);
if (whitespaceEnd >= line.length) {
// Only create a break token when we already have previous tokens
if (tokens.length > 0) {
const position = Position.create(currentLine, currentCharacter);
tokens.push({
type: 'break',
content: '',
range: Range.create(position, position)
});
}
}
else {
tagRegex.lastIndex = index;
const tagMatch = tagRegex.exec(line);
if (tagMatch) {
const fullMatch = tagMatch[0];
const value = tagMatch[1];
const start = Position.create(currentLine, currentCharacter + index);
const end = Position.create(currentLine, currentCharacter + index + fullMatch.length);
tokens.push({
type: 'tag',
content: value,
range: Range.create(start, end)
});
index += fullMatch.length;
index = skipWhitespace(line, index);
}
if (index < line.length) {
const rest = line.substring(index);
const inlineTagMatches = Array.from(rest.matchAll(inlineTagRegex));
tokens.push(...buildInlineTokens(inlineTagMatches, rest, currentLine, currentCharacter + index));
}
}
currentLine++;
currentCharacter = 0;
}
// Remove last break token if there is one
if (tokens.length > 0 && tokens[tokens.length - 1].type === 'break') {
return tokens.slice(0, -1);
}
return tokens;
}
function buildInlineTokens(tags, line, lineIndex, characterIndex) {
const tokens = [];
if (tags.length === 0) {
const start = Position.create(lineIndex, characterIndex);
const end = Position.create(lineIndex, characterIndex + line.length);
tokens.push({
type: 'text',
content: line,
range: Range.create(start, end)
});
}
else {
let lastIndex = 0;
for (const match of tags) {
const matchIndex = match.index;
const startContent = line.substring(lastIndex, matchIndex);
if (startContent.length > 0) {
tokens.push({
type: 'text',
content: line.substring(lastIndex, matchIndex),
range: Range.create(Position.create(lineIndex, lastIndex + characterIndex), Position.create(lineIndex, matchIndex + characterIndex))
});
}
let offset = startContent.length + 1;
const tagName = match[1];
tokens.push({
type: 'inline-tag',
content: tagName,
range: Range.create(Position.create(lineIndex, lastIndex + offset + characterIndex), Position.create(lineIndex, lastIndex + offset + tagName.length + characterIndex))
});
offset += tagName.length;
if (match.length === 4) {
offset += match[2].length;
const value = match[3];
tokens.push({
type: 'text',
content: value,
range: Range.create(Position.create(lineIndex, lastIndex + offset + characterIndex), Position.create(lineIndex, lastIndex + offset + value.length + characterIndex))
});
}
else {
tokens.push({
type: 'text',
content: '',
range: Range.create(Position.create(lineIndex, lastIndex + offset + characterIndex), Position.create(lineIndex, lastIndex + offset + characterIndex))
});
}
lastIndex = matchIndex + match[0].length;
}
const endContent = line.substring(lastIndex);
if (endContent.length > 0) {
tokens.push({
type: 'text',
content: endContent,
range: Range.create(Position.create(lineIndex, lastIndex + characterIndex), Position.create(lineIndex, lastIndex + characterIndex + endContent.length))
});
}
}
return tokens;
}
const nonWhitespaceRegex = /\S/;
const whitespaceEndRegex = /\s*$/;
function skipWhitespace(line, index) {
const match = line.substring(index).match(nonWhitespaceRegex);
if (match) {
return index + match.index;
}
else {
return line.length;
}
}
function lastCharacter(line) {
const match = line.match(whitespaceEndRegex);
if (match && typeof match.index === 'number') {
return match.index;
}
return undefined;
}
// Parsing
function parseJSDocComment(context) {
const startPosition = Position.create(context.position.line, context.position.character);
if (context.tokens.length === 0) {
return new JSDocCommentImpl([], Range.create(startPosition, startPosition));
}
const elements = [];
while (context.index < context.tokens.length) {
const element = parseJSDocElement(context, elements[elements.length - 1]);
if (element) {
elements.push(element);
}
}
const start = elements[0]?.range.start ?? startPosition;
const end = elements[elements.length - 1]?.range.end ?? startPosition;
return new JSDocCommentImpl(elements, Range.create(start, end));
}
function parseJSDocElement(context, last) {
const next = context.tokens[context.index];
if (next.type === 'tag') {
return parseJSDocTag(context, false);
}
else if (next.type === 'text' || next.type === 'inline-tag') {
return parseJSDocText(context);
}
else {
appendEmptyLine(next, last);
context.index++;
return undefined;
}
}
function appendEmptyLine(token, element) {
if (element) {
const line = new JSDocLineImpl('', token.range);
if ('inlines' in element) {
element.inlines.push(line);
}
else {
element.content.inlines.push(line);
}
}
}
function parseJSDocText(context) {
let token = context.tokens[context.index];
const firstToken = token;
let lastToken = token;
const lines = [];
while (token && token.type !== 'break' && token.type !== 'tag') {
lines.push(parseJSDocInline(context));
lastToken = token;
token = context.tokens[context.index];
}
return new JSDocTextImpl(lines, Range.create(firstToken.range.start, lastToken.range.end));
}
function parseJSDocInline(context) {
const token = context.tokens[context.index];
if (token.type === 'inline-tag') {
return parseJSDocTag(context, true);
}
else {
return parseJSDocLine(context);
}
}
function parseJSDocTag(context, inline) {
const tagToken = context.tokens[context.index++];
const name = tagToken.content.substring(1);
const nextToken = context.tokens[context.index];
if (nextToken?.type === 'text') {
if (inline) {
const docLine = parseJSDocLine(context);
return new JSDocTagImpl(name, new JSDocTextImpl([docLine], docLine.range), inline, Range.create(tagToken.range.start, docLine.range.end));
}
else {
const textDoc = parseJSDocText(context);
return new JSDocTagImpl(name, textDoc, inline, Range.create(tagToken.range.start, textDoc.range.end));
}
}
else {
const range = tagToken.range;
return new JSDocTagImpl(name, new JSDocTextImpl([], range), inline, range);
}
}
function parseJSDocLine(context) {
const token = context.tokens[context.index++];
return new JSDocLineImpl(token.content, token.range);
}
function normalizeOptions(options) {
if (!options) {
return normalizeOptions({
start: '/**',
end: '*/',
line: '*'
});
}
const { start, end, line } = options;
return {
start: normalizeOption(start, true),
end: normalizeOption(end, false),
line: normalizeOption(line, true)
};
}
function normalizeOption(option, start) {
if (typeof option === 'string' || typeof option === 'object') {
const escaped = typeof option === 'string' ? escapeRegExp(option) : option.source;
if (start) {
return new RegExp(`^\\s*${escaped}`);
}
else {
return new RegExp(`\\s*${escaped}\\s*$`);
}
}
else {
return option;
}
}
class JSDocCommentImpl {
constructor(elements, range) {
this.elements = elements;
this.range = range;
}
getTag(name) {
return this.getAllTags().find(e => e.name === name);
}
getTags(name) {
return this.getAllTags().filter(e => e.name === name);
}
getAllTags() {
return this.elements.filter(e => 'name' in e);
}
toString() {
let value = '';
for (const element of this.elements) {
if (value.length === 0) {
value = element.toString();
}
else {
const text = element.toString();
value += fillNewlines(value) + text;
}
}
return value.trim();
}
toMarkdown(options) {
let value = '';
for (const element of this.elements) {
if (value.length === 0) {
value = element.toMarkdown(options);
}
else {
const text = element.toMarkdown(options);
value += fillNewlines(value) + text;
}
}
return value.trim();
}
}
class JSDocTagImpl {
constructor(name, content, inline, range) {
this.name = name;
this.content = content;
this.inline = inline;
this.range = range;
}
toString() {
let text = `@${this.name}`;
const content = this.content.toString();
if (this.content.inlines.length === 1) {
text = `${text} ${content}`;
}
else if (this.content.inlines.length > 1) {
text = `${text}\n${content}`;
}
if (this.inline) {
// Inline tags are surrounded by curly braces
return `{${text}}`;
}
else {
return text;
}
}
toMarkdown(options) {
return options?.renderTag?.(this) ?? this.toMarkdownDefault(options);
}
toMarkdownDefault(options) {
const content = this.content.toMarkdown(options);
if (this.inline) {
const rendered = renderInlineTag(this.name, content, options ?? {});
if (typeof rendered === 'string') {
return rendered;
}
}
let marker = '';
if (options?.tag === 'italic' || options?.tag === undefined) {
marker = '*';
}
else if (options?.tag === 'bold') {
marker = '**';
}
else if (options?.tag === 'bold-italic') {
marker = '***';
}
let text = `${marker}@${this.name}${marker}`;
if (this.content.inlines.length === 1) {
text = `${text}${content}`;
}
else if (this.content.inlines.length > 1) {
text = `${text}\n${content}`;
}
if (this.inline) {
// Inline tags are surrounded by curly braces
return `{${text}}`;
}
else {
return text;
}
}
}
function renderInlineTag(tag, content, options) {
if (tag === 'linkplain' || tag === 'linkcode' || tag === 'link') {
const index = content.indexOf(' ');
let display = content;
if (index > 0) {
const displayStart = skipWhitespace(content, index);
display = content.substring(displayStart);
content = content.substring(0, index);
}
if (tag === 'linkcode' || (tag === 'link' && options.link === 'code')) {
// Surround the display value in a markdown inline code block
display = `\`${display}\``;
}
const renderedLink = options.renderLink?.(content, display) ?? renderLinkDefault(content, display);
return renderedLink;
}
return undefined;
}
function renderLinkDefault(content, display) {
try {
URI.parse(content, true);
return `[${display}](${content})`;
}
catch {
return content;
}
}
class JSDocTextImpl {
constructor(lines, range) {
this.inlines = lines;
this.range = range;
}
toString() {
let text = '';
for (let i = 0; i < this.inlines.length; i++) {
const inline = this.inlines[i];
const next = this.inlines[i + 1];
text += inline.toString();
if (next && next.range.start.line > inline.range.start.line) {
text += '\n';
}
}
return text;
}
toMarkdown(options) {
let text = '';
for (let i = 0; i < this.inlines.length; i++) {
const inline = this.inlines[i];
const next = this.inlines[i + 1];
text += inline.toMarkdown(options);
if (next && next.range.start.line > inline.range.start.line) {
text += '\n';
}
}
return text;
}
}
class JSDocLineImpl {
constructor(text, range) {
this.text = text;
this.range = range;
}
toString() {
return this.text;
}
toMarkdown() {
return this.text;
}
}
function fillNewlines(text) {
if (text.endsWith('\n')) {
return '\n';
}
else {
return '\n\n';
}
}
//# sourceMappingURL=jsdoc.js.map
File diff suppressed because one or more lines are too long
+967
View File
@@ -0,0 +1,967 @@
/******************************************************************************
* 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 { AstNode, Properties } from '../syntax-tree.js';
import type { SourceRegion, TraceRegion, TraceSourceSpec } from './generator-tracing.js';
export declare const EOL: string;
/**
* Common type of expected results of functions contributing to code generation.
* Includes `undefined` for allowing contributing functions to explicitly contribute
* nothing, if required, in contrast to contributing empty strings,
* which facilitates better formatting of the desired output, for example.
*/
export type Generated = GeneratorNode | string | undefined;
export type GeneratorNode = CompositeGeneratorNode | IndentNode | NewLineNode;
export interface IndentConfig {
indentedChildren?: Generated[] | ((indented: IndentNode) => void);
indentation?: string | number;
indentEmptyLines?: boolean;
indentImmediately?: boolean;
}
export declare function isGeneratorNode(node: unknown): node is CompositeGeneratorNode | IndentNode | NewLineNode;
export declare function isNewLineNode(node: unknown): node is NewLineNode;
/**
* Converts instances of {@link GeneratorNode} into a `string`, defaults to {@link String String(...)} for any other `input`.
*
* @param defaultIndentation the indentation to be applied if no explicit indentation is configured
* for particular {@link IndentNode IndentNodes}, either a `string` or a `number` of repeated single spaces,
* defaults to 4 single spaces, see {@link processGeneratorNode} -> `Context`.
*
* @returns the plain `string` represented by the given input.
*/
export declare function toString(input: unknown, defaultIndentation?: string | number): string;
/**
* Converts instances of {@link GeneratorNode} into `text` accompanied by a corresponding `trace`.
*
* @param defaultIndentation the indentation to be applied if no explicit indentation is configured
* for particular {@link IndentNode IndentNodes}, either a `string` or a `number` of repeated single spaces,
* defaults to 4 single spaces, see {@link processGeneratorNode} -> `Context`.
*
* @returns an object of type `{ text: string, trace: TraceRegion }` containing the desired `text` and `trace` data
*/
export declare function toStringAndTrace(input: GeneratorNode, defaultIndentation?: string | number): {
text: string;
trace: TraceRegion;
};
/**
* Implementation of {@link GeneratorNode} serving as container for `string` segments, {@link NewLineNode newline indicators},
* and further {@link CompositeGeneratorNode CompositeGeneratorNodes}, esp. {@link IndentNode IndentNodes}.
*
* See usage examples in the `append...` methods' documentations for details.
*/
export declare class CompositeGeneratorNode {
readonly contents: Array<(GeneratorNode | string)>;
tracedSource?: TraceSourceSpec | SourceRegion | SourceRegion[];
/**
* Constructor.
*
* @param content a var arg mixture of `strings` and {@link GeneratorNode GeneratorNodes}
* describing the initial content of this {@link CompositeGeneratorNode}
*
* @example
* new CompositeGeneratorNode(
* 'Hello World!', NL
* );
*/
constructor(...content: Generated[]);
isEmpty(): boolean;
/**
* Adds tracing information in form of `{astNode, property?, index: undefined}` to `this` generator node.
* Overwrites existing trace data, if set previously.
*
* The given data are kept as they are, the actual resolution of text positions within the DSL text
* is done at the final processing of `this` node as part of {@link toStringAndTrace()}.
*
* @param astNode the AstNode corresponding to `this` node's content
*
* @param property the value property name (string) corresponding to `this` node's content,
* e.g. if this node's content corresponds to some `string` or `number` property; is optional
*
* @returns `this` {@link CompositeGeneratorNode} for convenience.
*/
trace<T extends AstNode>(astNode: T, property?: Properties<T>): this;
/**
* Adds tracing information in form of `{astNode, property, index}` to `this` generator node.
* Overwrites existing trace data, if set previously.
*
* The given data are kept as they are, the actual resolution of text positions within the DSL text
* is done at the final processing of `this` node as part of {@link toStringAndTrace()}.
*
* @param astNode the AstNode corresponding to `this` node's content
*
* @param property the value property name (string) corresponding to `this` node's content,
* e.g. if this node's content corresponds to some `string` or `number` property
*
* @param index the index of the value within a list property corresponding to `this` node's content,
* if the property contains a list of elements, is ignored otherwise
*
* @returns `this` {@link CompositeGeneratorNode} for convenience.
*/
trace<T extends AstNode>(astNode: T, property: Properties<T>, index: number | undefined): this;
/**
* Adds tracing information in form of concrete coordinates to `this` generator node. Complete coordinates
* are provided by the {@link AstNode AstNodes}' corresponding {@link AstNode.$cstNode AstNode.$cstNodes}.
* Overwrites existing trace data, if set previously.
*
* @param sourceRegion a text region within some file in form of concrete coordinates,
* if `undefined` no tracing will happen
*
* @returns `this` {@link CompositeGeneratorNode} for convenience.
*/
trace(sourceRegion: SourceRegion | undefined): this;
/**
* Adds tracing information in form of a list of concrete coordinates to `this` generator node.
* Overwrites existing trace data, if set previously.
*
* The list of regions in `sourceRegion` is reduced to the smallest encompassing region
* of all the contained source regions.
*
* @param sourceRegion a list of text regions within some file in form of concrete coordinates,
* if empty no tracing will happen
*
* @returns `this` {@link CompositeGeneratorNode} for convenience.
*/
trace(sourceRegion: SourceRegion[]): this;
/**
* Appends `strings` and instances of {@link GeneratorNode} to `this` generator node.
*
* @param content a var arg mixture of `strings`, {@link GeneratorNode GeneratorNodes}, or single param
* functions that are immediately called with `this` node as argument, and which may append elements themselves.
*
* @returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().append(
* 'Hello', ' ', `${name}!`, NL, someOtherNode, 'NL', node => generateContent(node)
* ).append(
* 'The end!'
* );
*/
append(...content: Array<Generated | ((node: this) => void)>): this;
/**
* Prepends `strings` and instances of {@link GeneratorNode} to the content of `this` generator node.
*
* @param content a var arg mixture of `strings` or {@link GeneratorNode GeneratorNodes}.
*
* @returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* generateSomeContent()?.prepend(
* 'Some preamble text:', NL
* ).append(
* 'Some postamble text:', NL
* );
*/
prepend(...content: Generated[]): this;
/**
* Appends `strings` and instances of {@link GeneratorNode} to `this` generator node, if `condition` is equal to `true`.
*
* If `condition` is satisfied this method delegates to {@link append}, otherwise it returns just `this`.
*
* @param condition a boolean value indicating whether to append the elements of `args` to `this`.
*
* @param content a var arg mixture of `strings`, {@link GeneratorNode GeneratorNodes}, or single param
* functions that are immediately called with `this` node as argument, and which may append elements themselves.
*
* @returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().append(
* 'Hello World!'
* ).appendNewLine().appendIf(
* entity !== undefined, `Hello ${entity?.name}!`
* ).appendNewLineIfNotEmpty();
*/
appendIf(condition: boolean, ...content: Array<Generated | ((node: CompositeGeneratorNode) => void)>): this;
/**
* Prepends `strings` and instances of {@link GeneratorNode} to the content of `this` generator node, if `condition` is equal to `true`.
*
* If `condition` is satisfied this method delegates to {@link prepend}, otherwise it returns just `this`.
*
* @param condition a boolean value indicating whether to prepend the elements of `args` to the content of `this`.
*
* @param content a var arg mixture of `strings` or {@link GeneratorNode GeneratorNodes}.
*
* @returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* generateSomeContent()?.prependIf(
* generatePreamble === true,
* 'Some preamble', NL
* ).appendIf(
* generatePostamble === true,
* 'Some postamble', NL
* );
*/
prependIf(condition: boolean, ...content: Generated[]): this;
/**
* Appends a strict {@link NewLineNode} to `this` node.
* Strict {@link NewLineNode}s yield mandatory linebreaks in the derived generated text.
*
* @returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().append(
* 'Hello World!'
* ).appendNewLine();
*/
appendNewLine(): this;
/**
* Appends a strict {@link NewLineNode} to `this` node, if `condition` is equal to `true`.
* Strict {@link NewLineNode}s yield mandatory linebreaks in the derived generated text.
*
* @param condition a boolean value indicating whether to append a {@link NewLineNode} to `this`.
*
* @returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().append(
* 'Hello World!'
* ).appendNewLineIf(entity !== undefined).appendIf(
* entity !== undefined, `Hello ${entity?.name}!`
* )
*/
appendNewLineIf(condition: boolean): this;
/**
* Appends a soft {@link NewLineNode} to `this` node.
* Soft {@link NewLineNode}s yield linebreaks in the derived generated text only if the preceding line is non-empty,
* i.e. there are non-whitespace characters added to the generated text since the last linebreak.
*
* @returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().appendIf(
* entity !== undefined, `Hello ${entity?.name}!`
* ).appendNewLineIfNotEmpty();
*/
appendNewLineIfNotEmpty(): this;
/**
* Appends a soft {@link NewLineNode} to `this` node, if `condition` is equal to `true`.
* Soft {@link NewLineNode}s yield linebreaks in the derived generated text only if the preceding line is non-empty,
* i.e. there are non-whitespace characters added to the generated text since the last linebreak.
*
* @param condition a boolean value indicating whether to append a {@link NewLineNode} to `this`.
*
* @returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().append(
* entity.label ?? ''
* ).appendNewLineIfNotEmptyIf(entity.description !== undefined).append(
* entity.description
* )
*/
appendNewLineIfNotEmptyIf(condition: boolean): this;
/**
* Convenience method for appending content in form of a template to `this` generation node.
*
* See {@link expandToNode} for details.
*
* @returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().appendTemplate
* `Hello World!`
* .appendNewLine()
*/
appendTemplate(staticParts: TemplateStringsArray, ...substitutions: unknown[]): this;
/**
* Convenience method for appending content in form of a template to `this` generator node, if `condition` is equal to `true`.
*
* This method returns a tag function that takes the desired template and does the processing.
*
* If `condition` is satisfied the tagged template delegates to {@link appendTemplate}, otherwise it returns just `this`.
*
* See {@link expandToNode} for details.
*
* @param condition a boolean value indicating whether to append the template content to `this`.
*
* @returns a tag function behaving as described above, which in turn returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().appendTemplate
* `Hello World!`
* .appendNewLine().appendTemplateIf(entity !== undefined)
* `Hello ${entity?.name}!`
* .appendNewLineIfNotEmpty()
*/
appendTemplateIf(condition: boolean): (staticParts: TemplateStringsArray, ...substitutions: unknown[]) => this;
/**
* Adds an area of indented text output.
* The content to be indented can be provided as an array consisting of strings and/or generation nodes
* (undefined is permitted), or via a callback offering the `indentingNode` to which the content shall be appended.
* Alternatively, an object satisfying {@link IndentConfig} can be provided taking the children as Array or via
* a callback as described previously via the `indentedChildren` property.
*
* The remaining properties of {@link IndentConfig} have the following effects:
* - `indentation`: a specific indentation length or string, defaults to the global indentation setting if omitted, see {@link toString},
* - `indentEmptyLines`: apply indentation to empty lines, defaults to `false`
* - `indentImmediately`: apply the indentation immediately starting at the first line, defaults to `true`, might be set to `false`
* if preceding content is not terminated by any `newline`. If `false` the indentation is inserted only after child `newline` nodes
* followed by further content.
*
* @param childrenOrConfig an {@link Array} or callback contributing the children, or a config object satisfying {@link IndentConfig} alternatively.
*
* @returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().append(
* '{'
* ).indent(indentingNode =>
* indentingNode.append(
* 'name:', name, ','
* ).appendNewLine().appendIf(description !== undefined,
* 'description:', description
* ).appendNewLineIfNotEmpty()
* ).append(
* '}'
* );
*/
indent(childrenOrConfig?: Generated[] | ((indented: IndentNode) => void) | IndentConfig): this;
/**
* Convenience method for appending content to `this` generator node including tracing information
* in form of `{astNode, property?, index: undefined}`.
*
* This method returns a helper function that takes the desired `content` and does the processing.
* The returned function delegates to {@link append}, with the provided `content` being
* wrapped by an additional {@link CompositeGeneratorNode} configured with the tracing information.
*
* @param astNode the AstNode corresponding to the appended content
*
* @param property the value property name (string) corresponding to the appended content,
* if e.g. the content corresponds to some `string` or `number` property of `astNode`, is optional
*
* @returns a function behaving as described above, which in turn returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().appendTemplate
* `Hello World!`
* .appendNewLine().append('Hello ').appendTraced(entity, 'name')(entity.name)
* .appendNewLineIfNotEmpty()
*/
appendTraced<T extends AstNode>(astNode: T, property?: Properties<T>): (...content: Array<Generated | ((node: CompositeGeneratorNode) => void)>) => this;
/**
* Convenience method for appending content to `this` generator node including tracing information
* in form of `{astNode, property, index}`.
*
* This method returns a helper function that takes the desired `content` and does the processing.
* The returned function delegates to {@link append}, with the provided `content` being
* wrapped by an additional {@link CompositeGeneratorNode} configured with the tracing information.
*
* @param astNode the AstNode corresponding to the appended content
*
* @param property the value property name (string) corresponding to the appended content,
* if e.g. the content corresponds to some `string` or `number` property of `astNode`
*
* @param index the index of the value within a list property corresponding to the appended content,
* if the property contains a list of elements, is ignored otherwise
*
* @returns a function behaving as described above, which in turn returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().appendTemplate
* `Hello World!`
* .appendNewLine().append('Hello ').appendTraced(entity, 'name')(entity.name)
* .appendNewLineIfNotEmpty()
*/
appendTraced<T extends AstNode>(astNode: T, property: Properties<T>, index: number | undefined): (...content: Array<Generated | ((node: CompositeGeneratorNode) => void)>) => this;
/**
* Convenience method for appending content to `this` generator node including tracing information
* in form of concrete coordinates.
*
* This method returns a helper function that takes the desired `content` and does the processing.
* The returned function delegates to {@link append}, with the provided `content` being
* wrapped by an additional {@link CompositeGeneratorNode} configured with the tracing information.
*
* @param sourceRegion a text region within some file in form of concrete coordinates,
* if `undefined` no tracing will happen
*
* @returns a function behaving as described above, which in turn returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().appendTemplate
* `Hello World!`
* .appendNewLine().append('Hello ').appendTraced(findNodeForProperty(entity.$cstNode, 'name'))(entity.name)
* .appendNewLineIfNotEmpty()
*/
appendTraced(sourceRegion: SourceRegion | undefined): (...content: Array<Generated | ((node: CompositeGeneratorNode) => void)>) => this;
/**
* Convenience method for appending content to `this` generator node including tracing information
* in form of a list of concrete coordinates.
*
* This method returns a helper function that takes the desired `content` and does the processing.
* The returned function delegates to {@link append}, with the provided `content` being
* wrapped by an additional {@link CompositeGeneratorNode} configured with the tracing information.
*
* The list of regions in `sourceRegions` will later be reduced to the smallest encompassing region
* of all the contained source regions.
*
* @param sourceRegions a list of text regions within some file in form of concrete coordinates,
* if empty no tracing will happen
*
* @returns a function behaving as described above, which in turn returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().appendTemplate
* `Hello World!`
* .appendNewLine().append('Hello ').appendTraced([ findNodeForProperty(entity.$cstNode, 'name') ])(entity.name)
* .appendNewLineIfNotEmpty()
*/
appendTraced(sourceRegions: SourceRegion[]): (...content: Array<Generated | ((node: CompositeGeneratorNode) => void)>) => this;
/**
* Convenience method for appending content to `this` generator node including tracing information
* in form of `{astNode, property?, index: undefined}`, if `condition` is equal to `true`.
*
* This method returns a tag function that takes the desired template and does the processing.
*
* If `condition` is satisfied the returned function delegates to {@link appendTraced}, otherwise it returns just `this`.
*
* @param condition a boolean value indicating whether to append the template content to `this`.
*
* @param astNode the AstNode corresponding to the appended content
*
* @param property the value property name (string) corresponding to the appended content,
* if e.g. the content corresponds to some `string` or `number` property of `astNode`, is optional
*
* @returns a function behaving as described above, which in turn returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().appendTemplate
* `Hello World!`
* .appendNewLine().appendIf(entity !== undefined, 'Hello ').appendTracedIf(entity !== undefined, entity, 'name')(entity?.name)
* .appendNewLineIfNotEmpty()
*/
appendTracedIf<T extends AstNode>(condition: boolean, astNode: T, property?: Properties<T>): (...content: Array<Generated | ((node: CompositeGeneratorNode) => void)>) => this;
/**
* Convenience method for appending content to `this` generator node including tracing information
* in form of `{astNode, property, index}`, if `condition` is equal to `true`.
*
* This method returns a tag function that takes the desired template and does the processing.
*
* If `condition` is satisfied the returned function delegates to {@link appendTraced}, otherwise it returns just `this`.
*
* @param condition a boolean value indicating whether to append the template content to `this`.
*
* @param astNode the AstNode corresponding to the appended content
*
* @param property the value property name (string) corresponding to the appended content,
* if e.g. the content corresponds to some `string` or `number` property of `astNode`
*
* @param index the index of the value within a list property corresponding to the appended content,
* if the property contains a list of elements, is ignored otherwise
*
* @returns a function behaving as described above, which in turn returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().appendTemplate
* `Hello World!`
* .appendNewLine().appendIf(entity !== undefined, 'Hello ').appendTracedIf(entity !== undefined, entity, 'name')(entity?.name)
* .appendNewLineIfNotEmpty()
*/
appendTracedIf<T extends AstNode>(condition: boolean, astNode: T, property: Properties<T>, index: number | undefined): (...content: Array<Generated | ((node: CompositeGeneratorNode) => void)>) => this;
/**
* Convenience method for appending content to `this` generator node including tracing information
* in form of concrete coordinates, if `condition` is equal to `true`.
*
* This method returns a tag function that takes the desired template and does the processing.
*
* If `condition` is satisfied the returned function delegates to {@link appendTraced}, otherwise it returns just `this`.
*
* If `sourceRegion` is a function supplying the corresponding region, it's only called if `condition` is satisfied.
*
* @param condition a boolean value indicating whether to append the template content to `this`.
*
* @param sourceRegion a text region within some file in form of concrete coordinates,
* if `undefined` no tracing will happen
*
* @returns a function behaving as described above, which in turn returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().appendTemplate
* `Hello World!`
* .appendNewLine().appendIf(entity !== undefined, 'Hello ').appendTracedIf(entity !== undefined, () => findNodeForProperty(entity.$cstNode, 'name'))(entity?.name)
* .appendNewLineIfNotEmpty()
*/
appendTracedIf(condition: boolean, sourceRegion: SourceRegion | undefined | (() => SourceRegion | undefined)): (...content: Array<Generated | ((node: CompositeGeneratorNode) => void)>) => this;
/**
* Convenience method for appending content to `this` generator node including tracing information
* in form of a list of concrete coordinates, if `condition` is equal to `true`.
*
* This method returns a tag function that takes the desired template and does the processing.
*
* If `condition` is satisfied the returned function delegates to {@link appendTraced}, otherwise it returns just `this`.
*
* The list of regions in `sourceRegions` will later be reduced to the smallest encompassing region
* of all the contained source regions.
* If `sourceRegions` is a function supplying the corresponding regions, it's only called if `condition` is satisfied.
*
* @param condition a boolean value indicating whether to append the template content to `this`.
*
* @param sourceRegions a list of text regions within some file in form of concrete coordinates,
* if empty no tracing will happen
*
* @returns a function behaving as described above, which in turn returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().appendTemplate
* `Hello World!`
* .appendNewLine().appendIf(entity !== undefined, 'Hello ').appendTracedIf(entity !== undefined, () => [ findNodeForProperty(entity.$cstNode, 'name') ])(entity?.name)
* .appendNewLineIfNotEmpty()
*/
appendTracedIf(condition: boolean, sourceRegions: SourceRegion[] | (() => SourceRegion[])): (...content: Array<Generated | ((node: CompositeGeneratorNode) => void)>) => this;
/**
* Convenience method for appending content in form of a template to `this` generator node including tracing
* information in form of `{astNode, property?, index: undefined}`.
*
* This method returns a tag function that takes the desired template and does the processing by delegating to
* {@link expandTracedToNode}.
*
* @param astNode the AstNode corresponding to the appended content
*
* @param property the value property name (string) corresponding to the appended content,
* if e.g. the content corresponds to some `string` or `number` property of `astNode`, is optional
*
* @returns a tag function behaving as described above, which in turn returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().appendTemplate
* `Hello World!`
* .appendNewLine().appendTracedTemplate(entity, 'name')
* `Hello ${entity?.name}!`
* .appendNewLineIfNotEmpty()
*/
appendTracedTemplate<T extends AstNode>(astNode: T, property?: Properties<T>): (staticParts: TemplateStringsArray, ...substitutions: unknown[]) => this;
/**
* Convenience method for appending content in form of a template to `this` generator node including tracing
* information in form of `{astNode, property, index}`.
*
* This method returns a tag function that takes the desired template and does the processing by delegating to
* {@link expandTracedToNode}.
*
* @param astNode the AstNode corresponding to the appended content
*
* @param property the value property name (string) corresponding to the appended content,
* if e.g. the content corresponds to some `string` or `number` property of `astNode`
*
* @param index the index of the value within a list property corresponding to the appended content,
* if the property contains a list of elements, is ignored otherwise, is optinal
*
* @returns a tag function behaving as described above, which in turn returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().appendTemplate
* `Hello World!`
* .appendNewLine().appendTracedTemplate(entity, 'name')
* `Hello ${entity?.name}!`
* .appendNewLineIfNotEmpty()
*/
appendTracedTemplate<T extends AstNode>(astNode: T, property: Properties<T>, index: number | undefined): (staticParts: TemplateStringsArray, ...substitutions: unknown[]) => this;
/**
* Convenience method for appending content in form of a template to `this` generator node including tracing
* information in form of concrete coordinates.
*
* This method returns a tag function that takes the desired template and does the processing by delegating to
* {@link expandTracedToNode}.
*
* @param sourceRegion a text region within some file in form of concrete coordinates,
* if `undefined` no tracing will happen
*
* @returns a tag function behaving as described above, which in turn returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().appendTemplate
* `Hello World!`
* .appendNewLine().appendTracedTemplate(findNodeForProperty(entity.$cstNode, 'name'))
* `Hello ${entity?.name}!`
* .appendNewLineIfNotEmpty()
*/
appendTracedTemplate(sourceRegion: SourceRegion | undefined): (staticParts: TemplateStringsArray, ...substitutions: unknown[]) => this;
/**
* Convenience method for appending content in form of a template to `this` generator node including tracing
* information in form of concrete coordinates.
*
* This method returns a tag function that takes the desired template and does the processing by delegating to
* {@link expandTracedToNode}.
*
* The list of regions in `sourceRegions` will later be reduced to the smallest encompassing region
* of all the contained source regions.
*
* @param sourceRegions a list of text regions within some file in form of concrete coordinates,
* if empty no tracing will happen
*
* @returns a tag function behaving as described above, which in turn returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().appendTemplate
* `Hello World!`
* .appendNewLine().appendTracedTemplate( findNodesForProperty(entity.$cstNode, 'name'))
* `Hello ${entity?.name}!`
* .appendNewLineIfNotEmpty()
*/
appendTracedTemplate(sourceRegions: SourceRegion[]): (staticParts: TemplateStringsArray, ...substitutions: unknown[]) => this;
/**
* Convenience method for appending content in form of a template to `this` generator node including tracing information
* in form of `{astNode, property?, index: undefined}`, if `condition` is equal to `true`.
*
* This method returns a tag function that takes the desired template and does the processing.
*
* If `condition` is satisfied the tagged template delegates to {@link appendTracedTemplate}, otherwise it returns just `this`.
* See also {@link expandTracedToNode} for details.
*
* @param condition a boolean value indicating whether to append the template content to `this`.
*
* @param astNode the AstNode corresponding to the appended content
*
* @param property the value property name (string) corresponding to the appended content,
* if e.g. the content corresponds to some `string` or `number` property of `astNode`, is optional
*
* @returns a tag function behaving as described above, which in turn returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().appendTemplate
* `Hello World!`
* .appendNewLine().appendTracedTemplateIf(entity?.name !== undefined, entity)
* `Hello ${entity?.name}!`
* .appendNewLineIfNotEmpty()
*/
appendTracedTemplateIf<T extends AstNode>(condition: boolean, astNode: T, property?: Properties<T>): (staticParts: TemplateStringsArray, ...substitutions: unknown[]) => this;
/**
* Convenience method for appending content in form of a template to `this` generator node including tracing information
* in form of `{astNode, property, index}`, if `condition` is equal to `true`.
*
* This method returns a tag function that takes the desired template and does the processing.
*
* If `condition` is satisfied the tagged template delegates to {@link appendTracedTemplate}, otherwise it returns just `this`.
* See also {@link expandTracedToNode} for details.
*
* @param condition a boolean value indicating whether to append the template content to `this`.
*
* @param astNode the AstNode corresponding to the appended content
*
* @param property the value property name (string) corresponding to the appended content,
* if e.g. the content corresponds to some `string` or `number` property of `astNode`
*
* @param index the index of the value within a list property corresponding to the appended content,
* if the property contains a list of elements, is ignored otherwise
*
* @returns a tag function behaving as described above, which in turn returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().appendTemplate
* `Hello World!`
* .appendNewLine().appendTracedTemplateIf(entity?.name !== undefined, entity)
* `Hello ${entity?.name}!`
* .appendNewLineIfNotEmpty()
*/
appendTracedTemplateIf<T extends AstNode>(condition: boolean, astNode: T, property: Properties<T>, index: number | undefined): (staticParts: TemplateStringsArray, ...substitutions: unknown[]) => this;
/**
* Convenience method for appending content in form of a template to `this` generator node including tracing information
* in form of concrete coordinates, if `condition` is equal to `true`.
*
* This method returns a tag function that takes the desired template and does the processing.
*
* If `condition` is satisfied the tagged template delegates to {@link appendTracedTemplate}, otherwise it returns just `this`.
* See also {@link expandTracedToNode} for details.
*
* If `sourceRegion` is a function supplying the corresponding region, it's only called if `condition` is satisfied.
*
* @param condition a boolean value indicating whether to append the template content to `this`.
*
* @param sourceRegion a text region within some file in form of concrete coordinates,
* if `undefined` no tracing will happen
*
* @returns a tag function behaving as described above, which in turn returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().appendTemplate
* `Hello World!`
* .appendNewLine().appendTracedTemplateIf(entity?.name !== undefined, entity.$cstNode)
* `Hello ${entity?.name}!`
* .appendNewLineIfNotEmpty()
*/
appendTracedTemplateIf(condition: boolean, sourceRegion: SourceRegion | undefined | (() => SourceRegion | undefined)): (staticParts: TemplateStringsArray, ...substitutions: unknown[]) => this;
/**
* Convenience method for appending content in form of a template to `this` generator node including tracing information
* in form of a list of concrete coordinates, if `condition` is equal to `true`.
*
* This method returns a tag function that takes the desired template and does the processing.
*
* If `condition` is satisfied the tagged template delegates to {@link appendTracedTemplate}, otherwise it returns just `this`.
* See also {@link expandTracedToNode} for details.
*
* The list of regions in `sourceRegions` will later be reduced to the smallest encompassing region
* of all the contained source regions.
* If `sourceRegions` is a function supplying the corresponding regions, it's only called if `condition` is satisfied.
*
* @param condition a boolean value indicating whether to append the template content to `this`.
*
* @param sourceRegions a list of text regions within some file in form of concrete coordinates,
* if empty no tracing will happen
*
* @returns a tag function behaving as described above, which in turn returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().appendTemplate
* `Hello World!`
* .appendNewLine().appendTracedTemplateIf(entity?.name !== undefined, () => [ findNodeForProperty(entity.$cstNode, 'name')! ])
* `Hello ${entity?.name}!`
* .appendNewLineIfNotEmpty()
*/
appendTracedTemplateIf(condition: boolean, sourceRegions: SourceRegion[] | (() => SourceRegion[])): (staticParts: TemplateStringsArray, ...substitutions: unknown[]) => this;
}
/**
* Convenience function for attaching tracing information to content of type `Generated`,
* in form of `{astNode, property?, index: undefined}`.
*
* This method returns a helper function that takes the desired `content` and does the processing.
* The returned function will create and return a new {@link CompositeGeneratorNode} being initialized
* with the given tracing information and add some `content`, if provided.
*
* Exception: if `content` is already a {@link CompositeGeneratorNode} containing no tracing information,
* that node is enriched with the given tracing information and returned, and no wrapping node is created.
*
* @param astNode the AstNode corresponding to the appended content
*
* @param property the value property name (string) corresponding to the appended content,
* if e.g. the content corresponds to some `string` or `number` property of `astNode`, is optional
*
* @returns a function behaving as described above, which in turn returns a {@link CompositeGeneratorNode}.
*
* @example
* new CompositeGeneratorNode().appendTemplate
* `Hello World!`
* .appendNewLine().appendTracedTemplate(entity)
* `Hello ${ traceToNode(entity, 'name')(entity.name) }`
* .appendNewLineIfNotEmpty()
*/
export declare function traceToNode<T extends AstNode>(astNode: T, property?: Properties<T>): (content?: Generated | ((node: CompositeGeneratorNode) => void)) => CompositeGeneratorNode;
/**
* Convenience function for attaching tracing information to content of type `Generated`,
* in form of `{astNode, property, index}`.
*
* This method returns a helper function that takes the desired `content` and does the processing.
* The returned function will create and return a new {@link CompositeGeneratorNode} being initialized
* with the given tracing information and add some `content`, if provided.
*
* Exception: if `content` is already a {@link CompositeGeneratorNode} containing no tracing information,
* that node is enriched with the given tracing information and returned, and no wrapping node is created.
*
* @param astNode the AstNode corresponding to the appended content
*
* @param property the value property name (string) corresponding to the appended content,
* if e.g. the content corresponds to some `string` or `number` property of `astNode`
*
* @param index the index of the value within a list property corresponding to the appended content,
* if the property contains a list of elements, is ignored otherwise
*
* @returns a function behaving as described above, which in turn returns a {@link CompositeGeneratorNode}.
*
* @example
* new CompositeGeneratorNode().appendTemplate
* `Hello World!`
* .appendNewLine().appendTracedTemplate(entity)
* `Hello ${ traceToNode(entity, 'name')(entity.name) }`
* .appendNewLineIfNotEmpty()
*/
export declare function traceToNode<T extends AstNode>(astNode: T, property: Properties<T>, index: number | undefined): (content?: Generated | ((node: CompositeGeneratorNode) => void)) => CompositeGeneratorNode;
/**
* Convenience function for attaching tracing information to content of type `Generated`,
* in form of concrete coordinates.
*
* This method returns a helper function that takes the desired `content` and does the processing.
* The returned function will create and return a new {@link CompositeGeneratorNode} being initialized
* with the given tracing information and add some `content`, if provided.
*
* Exception: if `content` is already a {@link CompositeGeneratorNode} containing no tracing information,
* that node is enriched with the given tracing information and returned, and no wrapping node is created.
*
* @param sourceRegion a text region within some file in form of concrete coordinates,
* if `undefined` no tracing will happen
*
* @returns a function behaving as described above, which in turn returns a {@link CompositeGeneratorNode}.
*
* @example
* new CompositeGeneratorNode().appendTemplate
* `Hello World!`
* .appendNewLine().appendTracedTemplate(entity.$cstNode)
* `Hello ${ traceToNode(findNodeForProperty(entity.$cstNode, 'name'))(entity.name) }`
* .appendNewLineIfNotEmpty()
*/
export declare function traceToNode(sourceRegion: SourceRegion | undefined): (content?: Generated | ((node: CompositeGeneratorNode) => void)) => CompositeGeneratorNode;
/**
* Convenience function for attaching tracing information to content of type `Generated`,
* in form of a list of concrete coordinates.
*
* This method returns a helper function that takes the desired `content` and does the processing.
* The returned function will create and return a new {@link CompositeGeneratorNode} being initialized
* with the given tracing information and add some `content`, if provided.
*
* Exception: if `content` is already a {@link CompositeGeneratorNode} containing no tracing information,
* that node is enriched with the given tracing information and returned, and no wrapping node is created.
*
* The list of regions in `sourceRegions` will later be reduced to the smallest encompassing region
* of all the contained source regions.
*
* @param sourceRegions a list of text region within some file in form of concrete coordinates,
* if empty no tracing will happen
*
* @returns a function behaving as described above, which in turn returns a {@link CompositeGeneratorNode}.
*
* @example
* new CompositeGeneratorNode().appendTemplate
* `Hello World!`
* .appendNewLine().appendTracedTemplate(entity.$cstNode)
* `Hello ${ traceToNode(findNodesForProperty(entity.$cstNode, 'name'))(entity.name) }`
* .appendNewLineIfNotEmpty()
*/
export declare function traceToNode(sourceRegions: SourceRegion[]): (content?: Generated | ((node: CompositeGeneratorNode) => void)) => CompositeGeneratorNode;
/**
* Convenience function for attaching tracing information to content of type `Generated`,
* in form of `{astNode, property?, index: undefined}`, if `condition` is equal to `true`.
*
* If `condition` is satisfied, this method returns a helper function that takes the desired
* `content` and does the processing. The returned function will create and return a new
* {@link CompositeGeneratorNode} being initialized with the given tracing information and
* add some `content`, if provided. Otherwise, the returned function just returns `undefined`.
*
* Exception: if `content` is already a {@link CompositeGeneratorNode} containing no tracing information,
* that node is enriched with the given tracing information and returned, and no wrapping node is created.
*
* @param condition a boolean value indicating whether to apply the provided tracing information to the desired content.
*
* @param astNode the AstNode corresponding to the appended content
*
* @param property the value property name (string) corresponding to the appended content,
* if e.g. the content corresponds to some `string` or `number` property of `astNode`, is optional
*
* @returns a function behaving as described above, which in turn returns a {@link CompositeGeneratorNode}.
*
* @example
* new CompositeGeneratorNode().appendTemplate
* `Hello World!`
* .appendNewLine().appendTracedTemplate(entity)
* `Hello ${ traceToNodeIf(!!entity.name, entity, 'name')(entity.name) }`
* .appendNewLineIfNotEmpty()
*/
export declare function traceToNodeIf<T extends AstNode>(condition: boolean, astNode: T, property?: Properties<T>): (content?: Generated | ((node: CompositeGeneratorNode) => void)) => CompositeGeneratorNode | undefined;
/**
* Convenience function for attaching tracing information to content of type `Generated`,
* in form of `{astNode, property, index}`, if `condition` is equal to `true`.
*
* If `condition` is satisfied, this method returns a helper function that takes the desired
* `content` and does the processing. The returned function will create and return a new
* {@link CompositeGeneratorNode} being initialized with the given tracing information and
* add some `content`, if provided. Otherwise, the returned function just returns `undefined`.
*
* Exception: if `content` is already a {@link CompositeGeneratorNode} containing no tracing information,
* that node is enriched with the given tracing information and returned, and no wrapping node is created.
*
* @param condition a boolean value indicating whether to apply the provided tracing information to the desired content.
*
* @param astNode the AstNode corresponding to the appended content
*
* @param property the value property name (string) corresponding to the appended content,
* if e.g. the content corresponds to some `string` or `number` property of `astNode`
*
* @param index the index of the value within a list property corresponding to the appended content,
* if the property contains a list of elements, is ignored otherwise
*
* @returns a function behaving as described above, which in turn returns a {@link CompositeGeneratorNode}.
*
* @example
* new CompositeGeneratorNode().appendTemplate
* `Hello World!`
* .appendNewLine().appendTracedTemplate(entity)
* `Hello ${ traceToNodeIf(!!entity.name, entity, 'name')(entity.name) }`
* .appendNewLineIfNotEmpty()
*/
export declare function traceToNodeIf<T extends AstNode>(condition: boolean, astNode: T, property: Properties<T>, index: number | undefined): (content?: Generated | ((node: CompositeGeneratorNode) => void)) => CompositeGeneratorNode | undefined;
/**
* Convenience function for attaching tracing information to content of type `Generated`,
* in form of concrete coordinates, if `condition` is equal to `true`.
*
* If `condition` is satisfied, this method returns a helper function that takes the desired
* `content` and does the processing. The returned function will create and return a new
* {@link CompositeGeneratorNode} being initialized with the given tracing information and
* add some `content`, if provided. Otherwise, the returned function just returns `undefined`.
*
* Exception: if `content` is already a {@link CompositeGeneratorNode} containing no tracing information,
* that node is enriched with the given tracing information and returned, and no wrapping node is created.
*
* If `sourceRegions` is a function supplying the corresponding regions, it's only called if `condition` is satisfied.
*
* @param condition a boolean value indicating whether to apply the provided tracing information to the desired content.
*
* @param sourceRegion a text region within some file in form of concrete coordinates,
* if `undefined` no tracing will happen
*
* @returns a function behaving as described above, which in turn returns a {@link CompositeGeneratorNode}.
*
* @example
* new CompositeGeneratorNode().appendTemplate
* `Hello World!`
* .appendNewLine().appendTracedTemplate(entity.$cstNode)
* `Hello ${ traceToNodeIf(!!entity.name, () => findNodeForProperty(entity.$cstNode, 'name'))(entity.name) }`
* .appendNewLineIfNotEmpty()
*/
export declare function traceToNodeIf(condition: boolean, sourceRegion: SourceRegion | undefined | (() => SourceRegion | undefined)): (content?: Generated | ((node: CompositeGeneratorNode) => void)) => CompositeGeneratorNode | undefined;
/**
* Convenience function for attaching tracing information to content of type `Generated`,
* in form of a list of concrete coordinates, if `condition` is equal to `true`.
*
* If `condition` is satisfied, this method returns a helper function that takes the desired
* `content` and does the processing. The returned function will create and return a new
* {@link CompositeGeneratorNode} being initialized with the given tracing information and
* add some `content`, if provided. Otherwise, the returned function just returns `undefined`.
*
* Exception: if `content` is already a {@link CompositeGeneratorNode} containing no tracing information,
* that node is enriched with the given tracing information and returned, and no wrapping node is created.
*
* The list of regions in `sourceRegions` will later be reduced to the smallest encompassing region
* of all the contained source regions.
* If `sourceRegions` is a function supplying the corresponding regions, it's only called if `condition` is satisfied.
*
* @param condition a boolean value indicating whether to apply the provided tracing information to the desired content.
*
* @param sourceRegions a list of text region within some file in form of concrete coordinates,
* if empty no tracing will happen
*
* @returns a function behaving as described above, which in turn returns a {@link CompositeGeneratorNode}.
*
* @example
* new CompositeGeneratorNode().appendTemplate
* `Hello World!`
* .appendNewLine().appendTracedTemplate(entity.$cstNode)
* `Hello ${ traceToNodeIf(!!entity.name, () => findNodesForProperty(entity.$cstNode, 'name'))(entity.name) }`
* .appendNewLineIfNotEmpty()
*/
export declare function traceToNodeIf(condition: boolean, sourceRegions: SourceRegion[] | (() => SourceRegion[])): (content?: Generated | ((node: CompositeGeneratorNode) => void)) => CompositeGeneratorNode | undefined;
/**
* Implementation of @{link GeneratorNode} denoting areas within the desired generated text of common increased indentation.
*/
export declare class IndentNode extends CompositeGeneratorNode {
readonly indentation?: string;
readonly indentImmediately: boolean;
readonly indentEmptyLines: boolean;
constructor(indentation?: string | number, indentImmediately?: boolean, indentEmptyLines?: boolean);
}
export declare namespace NewLineNode {
/**
* Integer range type allowing to specify 1 to 6 consecutive line breaks, i.e. up to 5 empty lines with a single `NewLineNode`.
*/
type NoOfLineBreaks = 1 | 2 | 3 | 4 | 5 | 6;
}
/**
* Implementation of @{link GeneratorNode} denoting linebreaks in the desired generated text.
*/
export declare class NewLineNode {
readonly lineDelimiter: string;
readonly ifNotEmpty: boolean;
readonly count: NewLineNode.NoOfLineBreaks;
constructor(lineDelimiter?: string, ifNotEmpty?: boolean, count?: NewLineNode.NoOfLineBreaks);
}
export declare const NL: NewLineNode;
export declare const NLEmpty: NewLineNode;
//# sourceMappingURL=generator-node.d.ts.map
File diff suppressed because one or more lines are too long
+386
View File
@@ -0,0 +1,386 @@
/******************************************************************************
* 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 } from '../syntax-tree.js';
import { processGeneratorNode } from './node-processor.js';
import { expandToNode, expandTracedToNode } from './template-node.js';
export const EOL = (typeof process === 'undefined') ? '\n' : (process.platform === 'win32') ? '\r\n' : '\n';
export function isGeneratorNode(node) {
return node instanceof CompositeGeneratorNode
|| node instanceof IndentNode
|| node instanceof NewLineNode;
}
export function isNewLineNode(node) {
return node instanceof NewLineNode;
}
/**
* Converts instances of {@link GeneratorNode} into a `string`, defaults to {@link String String(...)} for any other `input`.
*
* @param defaultIndentation the indentation to be applied if no explicit indentation is configured
* for particular {@link IndentNode IndentNodes}, either a `string` or a `number` of repeated single spaces,
* defaults to 4 single spaces, see {@link processGeneratorNode} -> `Context`.
*
* @returns the plain `string` represented by the given input.
*/
export function toString(input, defaultIndentation) {
if (isGeneratorNode(input))
return processGeneratorNode(input, defaultIndentation).text;
else
return String(input);
}
/**
* Converts instances of {@link GeneratorNode} into `text` accompanied by a corresponding `trace`.
*
* @param defaultIndentation the indentation to be applied if no explicit indentation is configured
* for particular {@link IndentNode IndentNodes}, either a `string` or a `number` of repeated single spaces,
* defaults to 4 single spaces, see {@link processGeneratorNode} -> `Context`.
*
* @returns an object of type `{ text: string, trace: TraceRegion }` containing the desired `text` and `trace` data
*/
export function toStringAndTrace(input, defaultIndentation) {
return processGeneratorNode(input, defaultIndentation);
}
/**
* Implementation of {@link GeneratorNode} serving as container for `string` segments, {@link NewLineNode newline indicators},
* and further {@link CompositeGeneratorNode CompositeGeneratorNodes}, esp. {@link IndentNode IndentNodes}.
*
* See usage examples in the `append...` methods' documentations for details.
*/
export class CompositeGeneratorNode {
/**
* Constructor.
*
* @param content a var arg mixture of `strings` and {@link GeneratorNode GeneratorNodes}
* describing the initial content of this {@link CompositeGeneratorNode}
*
* @example
* new CompositeGeneratorNode(
* 'Hello World!', NL
* );
*/
constructor(...content) {
this.contents = [];
this.append(...content);
}
isEmpty() {
return this.contents.length === 0;
}
trace(source, property, index) {
if (isAstNode(source)) {
this.tracedSource = { astNode: source, property, index };
if (this.tracedSource.property === undefined && this.tracedSource.index !== undefined && this.tracedSource.index > -1) {
throw new Error("Generation support: 'property' argument must not be 'undefined' if a non-negative value is assigned to 'index' in 'CompositeGeneratorNode.trace(...)'.");
}
}
else {
this.tracedSource = source;
}
return this;
}
/**
* Appends `strings` and instances of {@link GeneratorNode} to `this` generator node.
*
* @param content a var arg mixture of `strings`, {@link GeneratorNode GeneratorNodes}, or single param
* functions that are immediately called with `this` node as argument, and which may append elements themselves.
*
* @returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().append(
* 'Hello', ' ', `${name}!`, NL, someOtherNode, 'NL', node => generateContent(node)
* ).append(
* 'The end!'
* );
*/
append(...content) {
for (const arg of content) {
if (typeof arg === 'function') {
arg(this);
}
else if (arg) {
this.contents.push(arg);
}
}
return this;
}
/**
* Prepends `strings` and instances of {@link GeneratorNode} to the content of `this` generator node.
*
* @param content a var arg mixture of `strings` or {@link GeneratorNode GeneratorNodes}.
*
* @returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* generateSomeContent()?.prepend(
* 'Some preamble text:', NL
* ).append(
* 'Some postamble text:', NL
* );
*/
prepend(...content) {
this.contents.unshift(...content.filter(c => c !== undefined));
return this;
}
/**
* Appends `strings` and instances of {@link GeneratorNode} to `this` generator node, if `condition` is equal to `true`.
*
* If `condition` is satisfied this method delegates to {@link append}, otherwise it returns just `this`.
*
* @param condition a boolean value indicating whether to append the elements of `args` to `this`.
*
* @param content a var arg mixture of `strings`, {@link GeneratorNode GeneratorNodes}, or single param
* functions that are immediately called with `this` node as argument, and which may append elements themselves.
*
* @returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().append(
* 'Hello World!'
* ).appendNewLine().appendIf(
* entity !== undefined, `Hello ${entity?.name}!`
* ).appendNewLineIfNotEmpty();
*/
appendIf(condition, ...content) {
return condition ? this.append(...content) : this;
}
/**
* Prepends `strings` and instances of {@link GeneratorNode} to the content of `this` generator node, if `condition` is equal to `true`.
*
* If `condition` is satisfied this method delegates to {@link prepend}, otherwise it returns just `this`.
*
* @param condition a boolean value indicating whether to prepend the elements of `args` to the content of `this`.
*
* @param content a var arg mixture of `strings` or {@link GeneratorNode GeneratorNodes}.
*
* @returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* generateSomeContent()?.prependIf(
* generatePreamble === true,
* 'Some preamble', NL
* ).appendIf(
* generatePostamble === true,
* 'Some postamble', NL
* );
*/
prependIf(condition, ...content) {
return condition ? this.prepend(...content) : this;
}
/**
* Appends a strict {@link NewLineNode} to `this` node.
* Strict {@link NewLineNode}s yield mandatory linebreaks in the derived generated text.
*
* @returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().append(
* 'Hello World!'
* ).appendNewLine();
*/
appendNewLine() {
return this.append(NL);
}
/**
* Appends a strict {@link NewLineNode} to `this` node, if `condition` is equal to `true`.
* Strict {@link NewLineNode}s yield mandatory linebreaks in the derived generated text.
*
* @param condition a boolean value indicating whether to append a {@link NewLineNode} to `this`.
*
* @returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().append(
* 'Hello World!'
* ).appendNewLineIf(entity !== undefined).appendIf(
* entity !== undefined, `Hello ${entity?.name}!`
* )
*/
appendNewLineIf(condition) {
return condition ? this.append(NL) : this;
}
/**
* Appends a soft {@link NewLineNode} to `this` node.
* Soft {@link NewLineNode}s yield linebreaks in the derived generated text only if the preceding line is non-empty,
* i.e. there are non-whitespace characters added to the generated text since the last linebreak.
*
* @returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().appendIf(
* entity !== undefined, `Hello ${entity?.name}!`
* ).appendNewLineIfNotEmpty();
*/
appendNewLineIfNotEmpty() {
return this.append(NLEmpty);
}
/**
* Appends a soft {@link NewLineNode} to `this` node, if `condition` is equal to `true`.
* Soft {@link NewLineNode}s yield linebreaks in the derived generated text only if the preceding line is non-empty,
* i.e. there are non-whitespace characters added to the generated text since the last linebreak.
*
* @param condition a boolean value indicating whether to append a {@link NewLineNode} to `this`.
*
* @returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().append(
* entity.label ?? ''
* ).appendNewLineIfNotEmptyIf(entity.description !== undefined).append(
* entity.description
* )
*/
appendNewLineIfNotEmptyIf(condition) {
return condition ? this.appendNewLineIfNotEmpty() : this;
}
/**
* Convenience method for appending content in form of a template to `this` generation node.
*
* See {@link expandToNode} for details.
*
* @returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().appendTemplate
* `Hello World!`
* .appendNewLine()
*/
appendTemplate(staticParts, ...substitutions) {
return this.append(expandToNode(staticParts, ...substitutions));
}
/**
* Convenience method for appending content in form of a template to `this` generator node, if `condition` is equal to `true`.
*
* This method returns a tag function that takes the desired template and does the processing.
*
* If `condition` is satisfied the tagged template delegates to {@link appendTemplate}, otherwise it returns just `this`.
*
* See {@link expandToNode} for details.
*
* @param condition a boolean value indicating whether to append the template content to `this`.
*
* @returns a tag function behaving as described above, which in turn returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().appendTemplate
* `Hello World!`
* .appendNewLine().appendTemplateIf(entity !== undefined)
* `Hello ${entity?.name}!`
* .appendNewLineIfNotEmpty()
*/
appendTemplateIf(condition) {
return condition ? (staticParts, ...substitutions) => this.appendTemplate(staticParts, ...substitutions) : () => this;
}
/**
* Adds an area of indented text output.
* The content to be indented can be provided as an array consisting of strings and/or generation nodes
* (undefined is permitted), or via a callback offering the `indentingNode` to which the content shall be appended.
* Alternatively, an object satisfying {@link IndentConfig} can be provided taking the children as Array or via
* a callback as described previously via the `indentedChildren` property.
*
* The remaining properties of {@link IndentConfig} have the following effects:
* - `indentation`: a specific indentation length or string, defaults to the global indentation setting if omitted, see {@link toString},
* - `indentEmptyLines`: apply indentation to empty lines, defaults to `false`
* - `indentImmediately`: apply the indentation immediately starting at the first line, defaults to `true`, might be set to `false`
* if preceding content is not terminated by any `newline`. If `false` the indentation is inserted only after child `newline` nodes
* followed by further content.
*
* @param childrenOrConfig an {@link Array} or callback contributing the children, or a config object satisfying {@link IndentConfig} alternatively.
*
* @returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().append(
* '{'
* ).indent(indentingNode =>
* indentingNode.append(
* 'name:', name, ','
* ).appendNewLine().appendIf(description !== undefined,
* 'description:', description
* ).appendNewLineIfNotEmpty()
* ).append(
* '}'
* );
*/
indent(childrenOrConfig) {
const { indentedChildren, indentation, indentEmptyLines, indentImmediately } = Array.isArray(childrenOrConfig) || typeof childrenOrConfig === 'function'
? { indentedChildren: childrenOrConfig }
: typeof childrenOrConfig === 'object' ? childrenOrConfig : {};
const node = new IndentNode(indentation, indentImmediately, indentEmptyLines);
this.contents.push(node);
if (Array.isArray(indentedChildren)) {
node.append(...indentedChildren);
}
else if (indentedChildren) {
node.append(indentedChildren);
}
return this;
}
// implementation:
appendTraced(source, property, index) {
return content => {
return this.append(new CompositeGeneratorNode().trace(source, property, index).append(content));
};
}
// implementation:
appendTracedIf(condition, source, property, index) {
return condition ? this.appendTraced((typeof source === 'function' ? source() : source), property, index) : () => this;
}
// implementation:
appendTracedTemplate(source, property, index) {
return (staticParts, ...substitutions) => {
return this.append(expandTracedToNode(source, property, index)(staticParts, ...substitutions));
};
}
// implementation:
appendTracedTemplateIf(condition, source, property, index) {
return condition ? this.appendTracedTemplate((typeof source === 'function' ? source() : source), property, index) : () => this;
}
}
// implementation
export function traceToNode(astNode, property, index) {
return content => {
if (content instanceof CompositeGeneratorNode && content.tracedSource === undefined) {
return content.trace(astNode, property, index);
}
else {
// a `content !== undefined` check is skipped here on purpose in order to let this method always return a result;
// dropping empty generator nodes is considered a post processing optimization.
return new CompositeGeneratorNode().trace(astNode, property, index).append(content);
}
};
}
// implementation
export function traceToNodeIf(condition, source, property, index) {
return condition ? traceToNode((typeof source === 'function' ? source() : source), property, index) : () => undefined;
}
/**
* Implementation of @{link GeneratorNode} denoting areas within the desired generated text of common increased indentation.
*/
export class IndentNode extends CompositeGeneratorNode {
constructor(indentation, indentImmediately = true, indentEmptyLines = false) {
super();
if (typeof (indentation) === 'string') {
this.indentation = indentation;
}
else if (typeof (indentation) === 'number') {
this.indentation = ''.padStart(indentation);
}
this.indentImmediately = indentImmediately;
this.indentEmptyLines = indentEmptyLines;
}
}
/**
* Implementation of @{link GeneratorNode} denoting linebreaks in the desired generated text.
*/
export class NewLineNode {
constructor(lineDelimiter = EOL, ifNotEmpty = false, count = 1) {
this.lineDelimiter = lineDelimiter;
this.ifNotEmpty = ifNotEmpty;
this.count = count;
}
}
export const NL = new NewLineNode();
export const NLEmpty = new NewLineNode(undefined, true);
//# sourceMappingURL=generator-node.js.map
File diff suppressed because one or more lines are too long
+40
View File
@@ -0,0 +1,40 @@
/******************************************************************************
* 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 { Range } from 'vscode-languageserver-textdocument';
import type { AstNode, CstNode } from '../syntax-tree.js';
import type { DocumentSegment } from '../workspace/documents.js';
export interface TraceSourceSpec {
astNode: AstNode;
property?: string;
index?: number;
}
export type SourceRegion = TextRegion | TextRegion2 | DocumentSegmentWithFileURI;
export interface TextRegion {
fileURI?: string;
offset: number;
end: number;
length?: number;
range?: Range;
}
interface TextRegion2 {
fileURI?: string;
offset: number;
length: number;
end?: number;
range?: Range;
}
export interface TraceRegion {
sourceRegion?: TextRegion;
targetRegion: TextRegion;
children?: TraceRegion[];
}
interface DocumentSegmentWithFileURI extends Omit<DocumentSegment, 'range'> {
fileURI?: string;
range?: Range;
}
export declare function getSourceRegion(sourceSpec: TraceSourceSpec | undefined | SourceRegion | SourceRegion[]): DocumentSegmentWithFileURI | CstNode | undefined;
export {};
//# sourceMappingURL=generator-tracing.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"generator-tracing.d.ts","sourceRoot":"","sources":["../../src/generate/generator-tracing.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAEhF,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,oCAAoC,CAAC;AAEhE,OAAO,KAAK,EAAE,OAAO,EAAE,OAAO,EAAkB,MAAM,mBAAmB,CAAC;AAI1E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAEjE,MAAM,WAAW,eAAe;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,MAAM,YAAY,GAAG,UAAU,GAAG,WAAW,GAAG,0BAA0B,CAAA;AAEhF,MAAM,WAAW,UAAU;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,KAAK,CAAC;CACjB;AAED,UAAU,WAAW;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,KAAK,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IACxB,YAAY,CAAC,EAAE,UAAU,CAAC;IAC1B,YAAY,EAAE,UAAU,CAAC;IACzB,QAAQ,CAAC,EAAE,WAAW,EAAE,CAAC;CAC5B;AAED,UAAU,0BAA2B,SAAQ,IAAI,CAAC,eAAe,EAAE,OAAO,CAAC;IACvE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,KAAK,CAAC;CACjB;AAKD,wBAAgB,eAAe,CAAC,UAAU,EAAE,eAAe,GAAG,SAAS,GAAG,YAAY,GAAG,YAAY,EAAE,GAAG,0BAA0B,GAAG,OAAO,GAAG,SAAS,CA0BzJ"}
+135
View File
@@ -0,0 +1,135 @@
/******************************************************************************
* 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 { getDocument } from '../utils/ast-utils.js';
import { findNodesForProperty } from '../utils/grammar-utils.js';
import { TreeStreamImpl } from '../utils/stream.js';
export function getSourceRegion(sourceSpec) {
if (!sourceSpec) {
return undefined;
}
else if ('astNode' in sourceSpec) {
return getSourceRegionOfAstNode(sourceSpec);
}
else if (Array.isArray(sourceSpec)) {
return sourceSpec.reduce(mergeDocumentSegment, undefined); // apply mergeDocumentSegment for single entry sourcSpec lists, too, thus start with 'undefined' as initial value
}
else {
// some special treatment of cstNodes for revealing the uri of the defining DSL text file
// is currently only done for single cstNode tracings, like "expandTracedToNode(source.$cstNode)`...`",
// is _not done_ for multi node tracings like below, see if case above
// joinTracedToNode( [
// findNodeForKeyword(source.$cstNode, '{')!,
// findNodeForKeyword(source.$cstNode, '}')!
// ] )(source.children, c => c.name)
const sourceRegion = sourceSpec;
const sourceFileURIviaCstNode = isCstNode(sourceRegion)
? getDocumentURIOrUndefined(sourceRegion?.root?.astNode ?? sourceRegion?.astNode) : undefined;
return copyDocumentSegment(sourceRegion, sourceFileURIviaCstNode);
}
}
function isCstNode(segment) {
return typeof segment !== 'undefined' && 'element' in segment && 'text' in segment;
}
function getDocumentURIOrUndefined(astNode) {
try {
return getDocument(astNode).uri.toString();
}
catch (_error) {
return undefined;
}
}
function getSourceRegionOfAstNode(sourceSpec) {
const { astNode, property, index } = sourceSpec ?? {};
const textRegion = astNode?.$cstNode ?? astNode?.$textRegion;
if (astNode === undefined || textRegion === undefined) {
return undefined;
}
else if (property === undefined) {
return copyDocumentSegment(textRegion, getDocumentURI(astNode));
}
else {
const getSingleOrCompoundRegion = (regions) => {
if (index !== undefined && index > -1 && Array.isArray(astNode[property])) {
return index < regions.length ? regions[index] : undefined;
}
else {
return regions.reduce(mergeDocumentSegment, undefined);
}
};
if (textRegion.assignments?.[property]) {
const region = getSingleOrCompoundRegion(textRegion.assignments[property]);
return region && copyDocumentSegment(region, getDocumentURI(astNode));
}
else if (astNode.$cstNode) {
const region = getSingleOrCompoundRegion(findNodesForProperty(astNode.$cstNode, property));
return region && copyDocumentSegment(region, getDocumentURI(astNode));
}
else {
return undefined;
}
}
}
function getDocumentURI(astNode) {
if (astNode.$cstNode) {
return getDocument(astNode)?.uri?.toString();
}
else if (astNode.$textRegion) {
return astNode.$textRegion.documentURI
|| new TreeStreamImpl(astNode, n => n.$container ? [n.$container] : []).find(n => n.$textRegion?.documentURI)?.$textRegion?.documentURI;
}
else {
return undefined;
}
}
function copyDocumentSegment(region, fileURI) {
const result = {
offset: region.offset,
end: region.end ?? region.offset + region.length,
length: region.length ?? region.end - region.offset,
};
if (region.range) {
result.range = region.range;
}
fileURI ?? (fileURI = region.fileURI);
if (fileURI) {
result.fileURI = fileURI;
}
return result;
}
function mergeDocumentSegment(prev, curr) {
if (!prev) {
return curr && copyDocumentSegment(curr);
}
else if (!curr) {
return prev && copyDocumentSegment(prev);
}
const prevEnd = prev.end ?? prev.offset + prev.length;
const currEnd = curr.end ?? curr.offset + curr.length;
const offset = Math.min(prev.offset, curr.offset);
const end = Math.max(prevEnd, currEnd);
const length = end - offset;
const result = {
offset, end, length,
};
if (prev.range && curr.range) {
result.range = {
start: curr.range.start.line < prev.range.start.line
|| curr.range.start.line === prev.range.start.line && curr.range.start.character < prev.range.start.character
? curr.range.start : prev.range.start,
end: curr.range.end.line > prev.range.end.line
|| curr.range.end.line === prev.range.end.line && curr.range.end.character > prev.range.end.character
? curr.range.end : prev.range.end
};
}
if (prev.fileURI || curr.fileURI) {
const prevURI = prev.fileURI;
const currURI = curr.fileURI;
const fileURI = prevURI && currURI && prevURI !== currURI ? `<unmergable text regions of ${prevURI}, ${currURI}>` : prevURI ?? currURI;
result.fileURI = fileURI;
}
return result;
}
//# sourceMappingURL=generator-tracing.js.map
File diff suppressed because one or more lines are too long
+13
View File
@@ -0,0 +1,13 @@
/******************************************************************************
* 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.
*
* @module langium/generate
*/
export * from './generator-node.js';
export type { SourceRegion, TextRegion, TraceRegion, TraceSourceSpec } from './generator-tracing.js';
export * from './node-joiner.js';
export * from './template-node.js';
export { expandToString, expandToStringLF, expandToStringLFWithNL, expandToStringWithNL, normalizeEOL } from './template-string.js';
//# sourceMappingURL=index.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/generate/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,cAAc,qBAAqB,CAAC;AACpC,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACrG,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,sBAAsB,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.
*
* @module langium/generate
*/
export * from './generator-node.js';
export * from './node-joiner.js';
export * from './template-node.js';
export { expandToString, expandToStringLF, expandToStringLFWithNL, expandToStringWithNL, normalizeEOL } from './template-string.js';
//# sourceMappingURL=index.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/generate/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,cAAc,qBAAqB,CAAC;AAEpC,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC"}
+308
View File
@@ -0,0 +1,308 @@
/******************************************************************************
* 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 { AstNode, Properties } from '../syntax-tree.js';
import { CompositeGeneratorNode, type Generated, NewLineNode } from './generator-node.js';
import type { SourceRegion } from './generator-tracing.js';
export interface JoinOptions<T, U extends T = T> {
/**
* A plain or type guard filter function.
*
* Benefit compared to pre-filtering the joined iterable: Original indices of the elements are preserved and forwarded to the `toGenerated` function.
*/
filter?: ((element: T, index: number, isLast: boolean) => boolean) | ((element: T, index: number, isLast: boolean) => element is U);
/** A fixed prefix or prefix computation function to be prepended before each element of the iterable. */
prefix?: Generated | ((element: U, index: number, isLast: boolean) => Generated | undefined);
/** A fixed suffix or suffix computation function to be appended after each element of the iterable. */
suffix?: Generated | ((element: U, index: number, isLast: boolean) => Generated | undefined);
/** A fixed element separator to be inserted between 2 consecutive non-undefined item representations incl. their suffixes and prefixes. */
separator?: Generated;
/**
* Activates appending of up 6 line breaks after a non-undefined element + suffix + separator if given.
*
* If `true` a single line break is appended.
*
* If a number `> 6` is required you can achieve that via the `separator` or `suffix` options,
* e.g. `separator: new CompositeGeneratorNode(calcLineBreaks(...))`.
*/
appendNewLineIfNotEmpty?: true | NewLineNode.NoOfLineBreaks;
/**
* Suppresses appending trailing line breaks after the last item in the iterable if activated via `appendNewLineIfNotEmpty`.
*/
skipNewLineAfterLastItem?: true;
}
/**
* Joins the elements of the given `iterable` of pre-computed instances of {@link Generated}
* by appending the results to a {@link CompositeGeneratorNode} being returned finally.
* Each individual element is tested to be a string, a {@link CompositeGeneratorNode},
* or `undefined` and included as is if that test is satisfied. Otherwise the result of
* applying {@link String} (string constructor) to the element is included.
*
* Note: empty strings being included in `iterable` are treated as ordinary string
* representations, while the value of `undefined` makes this function to ignore the
* corresponding item and no separator is appended, if configured.
*
* Examples:
* ```
* expandToNode`
* ${ joinToNode(['a', 'b'], { appendNewLineIfNotEmpty: true }) }
*
* ${ joinToNode(new Set(['a', undefined, getElementNode()]), { separator: ',', appendNewLineIfNotEmpty: true }) }
* `
* ```
*
* @param iterable an {@link Array} or {@link Iterable} providing the elements to be joined
*
* @param options optional config object for defining a `separator`, contributing specialized
* `prefix` and/or `suffix` providers, and activating conditional line-break insertion. In addition,
* a dedicated `filter` function can be provided that enables the provision of the original
* element indices to the aforementioned functions, if the list is to be filtered. If
* {@link Array.filter} would be applied to the original list, the indices will be those of the
* filtered list during subsequent processing that in particular will cause confusion when using
* the tracing variant of this function named ({@link joinTracedToNode}).
* @returns the resulting {@link CompositeGeneratorNode} representing `iterable`'s content
*/
export declare function joinToNode<Generated>(iterable: Iterable<Generated> | Generated[], options?: JoinOptions<Generated>): CompositeGeneratorNode | undefined;
/**
* Joins the elements of the given `iterable` by applying `toGenerated` to each element
* and appending the results to a {@link CompositeGeneratorNode} being returned finally.
*
* Note: empty strings being returned by `toGenerated` are treated as ordinary string
* representations, while the result of `undefined` makes this function to ignore the
* corresponding item and no separator is appended, if configured.
*
* Examples:
* ```
* expandToNode`
* ${ joinToNode(['a', 'b'], String, { appendNewLineIfNotEmpty: true }) }
*
* ${ joinToNode(new Set(['a', undefined, 'b']), e => e && String(e), { separator: ',', appendNewLineIfNotEmpty: true }) }
* `
* ```
*
* @param iterable an {@link Array} or {@link Iterable} providing the elements to be joined
*
* @param toGenerated a callback converting each individual element to a string, a
* {@link CompositeGeneratorNode}, or to `undefined` if to be omitted, defaults to the `identity`
* for strings, generator nodes, and `undefined`, and to {@link String} otherwise.
*
* @param options optional config object for defining a `separator`, contributing specialized
* `prefix` and/or `suffix` providers, and activating conditional line-break insertion. In addition,
* a dedicated `filter` function can be provided that enables the provision of the
* original element indices to the aforementioned functions, if the list is to be filtered. If
* {@link Array.filter} would be applied to the original list, the indices will be those of the
* filtered list during subsequent processing that in particular will cause confusion when using
* the tracing variant of this function named ({@link joinTracedToNode}).
* @returns the resulting {@link CompositeGeneratorNode} representing `iterable`'s content
*/
export declare function joinToNode<T>(iterable: Iterable<T> | T[], toGenerated?: ((element: T, index: number, isLast: boolean) => Generated), options?: JoinOptions<T>): CompositeGeneratorNode | undefined;
/**
* Joins the elements of the given `iterable` by applying `toGenerated` to each element
* and appending the results to a {@link CompositeGeneratorNode} being returned finally.
*
* Here the mandatory type guard `filter` function is used to filter the elements of
* `iterable` and to apply the `toGenerated` function as well as the optional
* `prefix` and `suffix` functions to the accepted items with the more specific type
* `U` and their original indices.
*
* Note: empty strings being returned by `toGenerated` are treated as ordinary string
* representations, while the result of `undefined` makes this function to ignore the
* corresponding item and no separator is appended, if configured.
*
* Example:
* ```
* expandToNode`
* ${ joinToNode([x, y], e => e.propertyOfX, { filter: (e): e is X => e.$type === 'X' }) }
* `
* ```
*
* @param iterable an {@link Array} or {@link Iterable} providing the elements to be joined
*
* @param toGenerated a callback converting each individual element to a string, a
* {@link CompositeGeneratorNode}, or to `undefined` if to be omitted, defaults to the `identity`
* for strings, generator nodes, and `undefined`, and to {@link String} otherwise.
*
* @param options config object including the here required type guard filter function, as well as
* optional `separator` and `prefix` and/or `suffix` providers, and activating conditional
* line-break insertion.
* In contrast to {@link Array.filter} the dedicated `filter` function enables the provision of the
* original element indices to `toGenerated` and the aforementioned functions, if the list is to be
* filtered.
* @returns the resulting {@link CompositeGeneratorNode} representing `iterable`'s content
*/
export declare function joinToNode<T, U extends T>(iterable: Iterable<T> | T[], toGenerated: ((element: U, index: number, isLast: boolean) => Generated), options: JoinOptions<T, U> & {
filter: (element: T, index: number, isLast: boolean) => element is U;
}): CompositeGeneratorNode | undefined;
/**
* Convenience function for joining the elements of some `iterable` and gathering tracing information.
*
* This function returns another function that does the processing, and that expects same list of
* arguments as expected by {@link joinToNode}, i.e. an `iterable`, a function `toGenerated`
* converting each element into a `Generated`, as well as some `options`.
*
* That function then joins the elements of `iterable` by delegating to {@link joinToNode}.
* Via {@link traceToNode} the resulting generator node is supplemented with the provided tracing
* information in form of `{astNode, property?, index?}`, and finally returned. In addition,
* if `property` is given each element's generator node representation is augmented with the
* provided tracing information plus the index of the element within `iterable`.
*
* @param astNode the AstNode corresponding to the appended content
*
* @param property the value property name (string) corresponding to the appended content,
* if e.g. the content corresponds to some `string` or `number` property of `astNode`, is optional
*
* @returns a function behaving as described above, which in turn returns a {@link CompositeGeneratorNode}.
*
* @example
* expandToNode`
* children: ${ joinTracedToNode(entity, 'children')(entity.children, child => child.name, { separator: ' ' }) };
* `.appendNewLine()
*/
export declare function joinTracedToNode<T extends AstNode>(astNode: T, property?: Properties<T>): <E>(iterable: Iterable<E> | E[], toGenerated?: ((element: E, index: number, isLast: boolean) => Generated) | JoinOptions<E>, options?: JoinOptions<E>) => CompositeGeneratorNode;
/**
* Convenience function for joining the elements of some `iterable` and gathering tracing information
* in form of concrete coordinates.
*
* This function returns another function that does the processing, and that expects same list of
* arguments as expected by {@link joinToNode}, i.e. an `iterable`, a function `toGenerated`
* converting each element into a `Generated`, as well as some `options`.
*
* That function then joins the elements of `iterable` by delegating to {@link joinToNode}.
* Via {@link traceToNode} the resulting generator node is supplemented with the provided tracing
* information, and finally returned. Elementwise tracing need to be implemented by client code
* within `toGenerated`, if required.
*
* @param sourceRegion a text region within some file in form of concrete coordinates,
* if `undefined` no tracing will happen
*
* @returns a function behaving as described above, which in turn returns a {@link CompositeGeneratorNode}.
*
* @example
* expandToNode`
* children: ${ joinTracedToNode(findNodesForProperty(entity.$cstNode, 'children'))(entity.children, child => child.name, { separator: ' ' }) };
* `.appendNewLine()
*/
export declare function joinTracedToNode(sourceRegion: SourceRegion | undefined): <E>(iterable: Iterable<E> | E[], toGenerated?: ((element: E, index: number, isLast: boolean) => Generated) | JoinOptions<E>, options?: JoinOptions<E>) => CompositeGeneratorNode;
/**
* Convenience function for joining the elements of some `iterable` and gathering tracing information
* in form of a list of concrete coordinates.
*
* This function returns another function that does the processing, and that expects same list of
* arguments as expected by {@link joinToNode}, i.e. an `iterable`, a function `toGenerated`
* converting each element into a `Generated`, as well as some `options`.
*
* That function then joins the elements of `iterable` by delegating to {@link joinToNode}.
* Via {@link traceToNode} the resulting generator node is supplemented with the provided tracing
* information, and finally returned. Elementwise tracing need to be implemented by client code
* within `toGenerated`, if required.
*
* The list of regions in `sourceRegions` will later be reduced to the smallest encompassing region
* of all the contained source regions.
*
* @param sourceRegions a list of text regions within some file in form of concrete coordinates,
* if empty no tracing will happen
*
* @returns a function behaving as described above, which in turn returns a {@link CompositeGeneratorNode}.
*
* @example
* expandToNode`
* children: ${ joinTracedToNode(findNodesForProperty(entity.$cstNode, 'children'))(entity.children, child => child.name, { separator: ' ' }) };
* `.appendNewLine()
*/
export declare function joinTracedToNode(sourceRegions: SourceRegion[]): <E>(iterable: Iterable<E> | E[], toGenerated?: ((element: E, index: number, isLast: boolean) => Generated) | JoinOptions<E>, options?: JoinOptions<E>) => CompositeGeneratorNode;
/**
* Convenience function for joining the elements of some `iterable` and gathering tracing information,
* if `condition` is equal to `true`.
*
* If `condition` is satisfied, this function returns another function that does the processing,
* and that expects same list of arguments as expected by {@link joinToNode}, i.e. an `iterable`,
* a function `toGenerated` converting each element into a `Generated`, as well as some `options`.
*
* That function then joins the elements of `iterable` by delegating to {@link joinToNode}.
* Via {@link traceToNode} the resulting generator node is supplemented with the provided tracing
* information, and finally returned. In addition, if `property` is given each element's
* generator node representation is augmented with the provided tracing information
* plus the index of the element within `iterable`.
*
* Otherwise, if `condition` is equal to false, the returned function just returns `undefined`.
*
* @param condition a boolean value indicating whether to evaluate the provided iterable.
*
* @param astNode the AstNode corresponding to the appended content
*
* @param property the value property name (string) corresponding to the appended content,
* if e.g. the content corresponds to some `string` or `number` property of `astNode`, is optional
*
* @returns a function behaving as described above, which in turn returns a {@link CompositeGeneratorNode} or `undefined`.
*
* @example
* expandToNode`
* children: ${ joinTracedToNode(entity, 'children')(entity.children, child => child.name, { separator: ' ' }) };
* `.appendNewLine()
*/
export declare function joinTracedToNodeIf<T extends AstNode>(condition: boolean, astNode: T, property?: Properties<T>): <E>(iterable: Iterable<E> | E[], toGenerated?: ((element: E, index: number, isLast: boolean) => Generated) | JoinOptions<E>, options?: JoinOptions<E>) => CompositeGeneratorNode | undefined;
/**
* Convenience function for joining the elements of some `iterable` and gathering tracing information
* in form of a list of concrete coordinates, if `condition` is equal to `true`.
*
* If `condition` is satisfied, this function returns another function that does the processing,
* and that expects same list of arguments as expected by {@link joinToNode}, i.e. an `iterable`,
* a function `toGenerated` converting each element into a `Generated`, as well as some `options`.
*
* That function then joins the elements of `iterable` by delegating to {@link joinToNode}.
* Via {@link traceToNode} the resulting generator node is supplemented with the provided tracing
* information, and finally returned. Element-wise tracing need to be implemented by client code
* within `toGenerated`, if required.
*
* Otherwise, if `condition` is equal to false, the returned function just returns `undefined`.
*
* If `sourceRegion` is a function supplying the corresponding regions, it's only called if `condition` is satisfied.
*
* @param condition a boolean value indicating whether to evaluate the provided iterable.
*
* @param sourceRegion a text region within some file in form of concrete coordinates or a supplier function,
* if `undefined` no tracing will happen
*
* @returns a function behaving as described above, which in turn returns a {@link CompositeGeneratorNode}.
*
* @example
* expandToNode`
* children: ${ joinTracedToNodeIf(entity !== undefined, () => entity.$cstNode)(entity.children, child => child.name, { separator: ' ' }) };
* `.appendNewLine()
*/
export declare function joinTracedToNodeIf(condition: boolean, sourceRegion: SourceRegion | undefined | (() => SourceRegion | undefined)): <E>(iterable: Iterable<E> | E[], toGenerated?: ((element: E, index: number, isLast: boolean) => Generated) | JoinOptions<E>, options?: JoinOptions<E>) => CompositeGeneratorNode | undefined;
/**
* Convenience function for joining the elements of some `iterable` and gathering tracing information
* in form of a list of concrete coordinates, if `condition` is equal to `true`.
*
* If `condition` is satisfied, this function returns another function that does the processing,
* and that expects same list of arguments as expected by {@link joinToNode}, i.e. an `iterable`,
* a function `toGenerated` converting each element into a `Generated`, as well as some `options`.
*
* That function then joins the elements of `iterable` by delegating to {@link joinToNode}.
* Via {@link traceToNode} the resulting generator node is supplemented with the provided tracing
* information, and finally returned. Element-wise tracing need to be implemented by client code
* within `toGenerated`, if required.
*
* Otherwise, if `condition` is equal to false, the returned function just returns `undefined`.
*
* The list of regions in `sourceRegions` will later be reduced to the smallest encompassing region
* of all the contained source regions.
* If `sourceRegions` is a function supplying the corresponding regions, it's only called if `condition` is satisfied.
*
* @param condition a boolean value indicating whether to evaluate the provided iterable.
*
* @param sourceRegions a list of text regions within some file in form of concrete coordinates or a supplier function,
* if empty no tracing will happen
*
* @returns a function behaving as described above, which in turn returns a {@link CompositeGeneratorNode}.
*
* @example
* expandToNode`
* children: ${ joinTracedToNodeIf(entity !== undefined, () => findNodesForProperty(entity.$cstNode, 'children'))(entity.children, child => child.name, { separator: ' ' }) };
* `.appendNewLine()
*/
export declare function joinTracedToNodeIf(condition: boolean, sourceRegions: SourceRegion[] | (() => SourceRegion[])): <E>(iterable: Iterable<E> | E[], toGenerated?: ((element: E, index: number, isLast: boolean) => Generated) | JoinOptions<E>, options?: JoinOptions<E>) => CompositeGeneratorNode | undefined;
//# sourceMappingURL=node-joiner.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"node-joiner.d.ts","sourceRoot":"","sources":["../../src/generate/node-joiner.ts"],"names":[],"mappings":"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"}
+67
View File
@@ -0,0 +1,67 @@
/******************************************************************************
* 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 { CompositeGeneratorNode, isGeneratorNode, NewLineNode, traceToNode } from './generator-node.js';
const defaultToGenerated = (e) => e === undefined || typeof e === 'string' || isGeneratorNode(e) ? e : String(e);
export function joinToNode(iterable, toGeneratedOrOptions = defaultToGenerated, options = {}) {
const toGenerated = typeof toGeneratedOrOptions === 'function' ? toGeneratedOrOptions : defaultToGenerated;
const { filter, prefix, suffix, separator, appendNewLineIfNotEmpty, skipNewLineAfterLastItem } = typeof toGeneratedOrOptions === 'object' ? toGeneratedOrOptions : options;
const prefixFunc = typeof prefix === 'function' ? prefix : (() => prefix);
const suffixFunc = typeof suffix === 'function' ? suffix : (() => suffix);
const doAppendNewLines = (node) => {
if (node.isEmpty()) {
// do nothing
return;
}
else if (appendNewLineIfNotEmpty === true || appendNewLineIfNotEmpty === 1) {
node.appendNewLineIfNotEmpty();
}
else {
node.append(new NewLineNode(undefined, true, appendNewLineIfNotEmpty));
}
};
return reduceWithIsLast(iterable, (node, it, i, isLast) => {
if (filter && !filter(it, i, isLast)) {
return node;
}
const content = toGenerated(it, i, isLast);
return content === undefined ? /* in this case don't append anything to */ node : /* otherwise: */ (node ?? (node = new CompositeGeneratorNode()))
.appendIf(!node.isEmpty(), separator)
.appendIf(
// append 'newLineIfNotEmpty' elements only if 'node' has some content already,
// as if the parent is an IndentNode with 'indentImmediately' set to 'false'
// the indentation is not properly applied to the first non-empty line of the (this) child node
appendNewLineIfNotEmpty !== undefined, doAppendNewLines)
.append(prefixFunc(it, i, isLast))
.append(content)
.append(suffixFunc(it, i, isLast));
})?.appendIf(!skipNewLineAfterLastItem && appendNewLineIfNotEmpty !== undefined, doAppendNewLines);
}
// implementation:
export function joinTracedToNode(source, property) {
return (iterable, toGeneratedOrOptions, options) => {
options ?? (options = typeof toGeneratedOrOptions === 'object' ? toGeneratedOrOptions : undefined);
const toGenerated = typeof toGeneratedOrOptions === 'function' ? toGeneratedOrOptions : defaultToGenerated;
return traceToNode(source, property)(joinToNode(iterable, source && property ? (element, index, isLast) => traceToNode(source, property, index)(toGenerated(element, index, isLast)) : toGenerated, options));
};
}
// implementation:
export function joinTracedToNodeIf(condition, source, property) {
return condition ? joinTracedToNode((typeof source === 'function' ? source() : source), property) : () => undefined;
}
function reduceWithIsLast(iterable, callbackfn, initial) {
const iterator = iterable[Symbol.iterator]();
let next = iterator.next();
let index = 0;
let result = initial;
while (!next.done) {
const nextNext = iterator.next();
result = callbackfn(result, next.value, index, Boolean(nextNext.done));
next = nextNext;
index++;
}
return result;
}
//# sourceMappingURL=node-joiner.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"node-joiner.js","sourceRoot":"","sources":["../../src/generate/node-joiner.ts"],"names":[],"mappings":"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"}
+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 { GeneratorNode } from './generator-node.js';
import type { TraceRegion } from './generator-tracing.js';
export declare function processGeneratorNode(node: GeneratorNode, defaultIndentation?: string | number): {
text: string;
trace: TraceRegion;
};
//# sourceMappingURL=node-processor.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"node-processor.d.ts","sourceRoot":"","sources":["../../src/generate/node-processor.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAGhF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAA4B,WAAW,EAAE,MAAM,wBAAwB,CAAC;AA2KpF,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,aAAa,EAAE,kBAAkB,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,WAAW,CAAA;CAAE,CAwBpI"}
+258
View File
@@ -0,0 +1,258 @@
/******************************************************************************
* 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 { CompositeGeneratorNode, IndentNode, NewLineNode } from './generator-node.js';
import { getSourceRegion } from './generator-tracing.js';
class Context {
constructor(defaultIndent) {
this.defaultIndentation = ' ';
this.pendingIndent = true;
this.currentIndents = [];
this.recentNonImmediateIndents = [];
this.traceData = [];
this.lines = [[]];
this.length = 0;
if (typeof defaultIndent === 'string') {
this.defaultIndentation = defaultIndent;
}
else if (typeof defaultIndent === 'number') {
this.defaultIndentation = ''.padStart(defaultIndent);
}
}
get content() {
return this.lines.map(e => e.join('')).join('');
}
get contentLength() {
return this.length;
}
get currentLineNumber() {
return this.lines.length - 1;
}
get currentLineContent() {
return this.lines[this.currentLineNumber].join('');
}
get currentPosition() {
return {
offset: this.contentLength,
line: this.currentLineNumber,
character: this.currentLineContent.length
};
}
append(value, isIndent) {
if (value.length > 0) {
const beforePos = isIndent && this.currentPosition;
this.lines[this.currentLineNumber].push(value);
this.length += value.length;
if (beforePos) {
this.indentPendingTraceRegions(beforePos);
}
}
}
indentPendingTraceRegions(before) {
for (let i = this.traceData.length - 1; i >= 0; i--) {
const tr = this.traceData[i];
if (tr.targetStart && tr.targetStart.offset === before.offset /* tr.targetStart.line == before.line && tr.targetStart.character === before.character*/)
tr.targetStart = this.currentPosition;
}
}
increaseIndent(node) {
this.currentIndents.push(node);
if (!node.indentImmediately) {
this.recentNonImmediateIndents.push(node);
}
}
decreaseIndent() {
this.currentIndents.pop();
}
get relevantIndents() {
return this.currentIndents.filter(i => !this.recentNonImmediateIndents.includes(i));
}
resetCurrentLine() {
this.length -= this.lines[this.currentLineNumber].join('').length;
this.lines[this.currentLineNumber] = [];
this.pendingIndent = true;
this.recentNonImmediateIndents.length = 0;
}
addNewLine() {
this.lines.push([]);
this.pendingIndent = true;
this.recentNonImmediateIndents.length = 0;
}
pushTraceRegion(sourceRegion) {
const region = createTraceRegion(sourceRegion, this.currentPosition, it => this.traceData[this.traceData.length - 1]?.children?.push(it));
this.traceData.push(region);
return region;
}
popTraceRegion(expected) {
const traceRegion = this.traceData.pop();
// the following assertion can be dropped once the tracing is considered stable
this.assertTrue(traceRegion === expected, 'Trace region mismatch!');
return traceRegion;
}
getParentTraceSourceFileURI() {
for (let i = this.traceData.length - 1; i > -1; i--) {
const fileUri = this.traceData[i].sourceRegion?.fileURI;
if (fileUri)
return fileUri;
}
return undefined;
}
assertTrue(condition, msg) {
if (!condition) {
throw new Error(msg);
}
}
}
function createTraceRegion(sourceRegion, targetStart, accept) {
const result = {
sourceRegion,
targetRegion: undefined,
children: [],
targetStart,
complete: (targetEnd) => {
result.targetRegion = {
offset: result.targetStart.offset,
end: targetEnd.offset,
length: targetEnd.offset - result.targetStart.offset,
range: {
start: {
line: result.targetStart.line,
character: result.targetStart.character
},
end: {
line: targetEnd.line,
character: targetEnd.character
},
}
};
delete result.targetStart;
if (result.children?.length === 0) {
delete result.children;
}
if (result.targetRegion?.length) {
accept(result);
}
delete result.complete;
return result;
}
};
return result;
}
export function processGeneratorNode(node, defaultIndentation) {
const context = new Context(defaultIndentation);
const trace = context.pushTraceRegion(undefined);
processNodeInternal(node, context);
context.popTraceRegion(trace);
trace.complete && trace.complete(context.currentPosition);
const singleChild = trace.children && trace.children.length === 1 ? trace.children[0] : undefined;
const singleChildTargetRegion = singleChild?.targetRegion;
const rootTargetRegion = trace.targetRegion;
if (singleChildTargetRegion && singleChild.sourceRegion
&& singleChildTargetRegion.offset === rootTargetRegion.offset
&& singleChildTargetRegion.length === rootTargetRegion.length) {
// some optimization:
// if (the root) `node` is traced (`singleChild.sourceRegion` !== undefined) and spans the entire `context.content`
// we skip the wrapping root trace object created above at the beginning of this method
return { text: context.content, trace: singleChild };
}
else {
return { text: context.content, trace };
}
}
function processNodeInternal(node, context) {
if (typeof (node) === 'string') {
processStringNode(node, context);
}
else if (node instanceof IndentNode) {
processIndentNode(node, context);
}
else if (node instanceof CompositeGeneratorNode) {
processCompositeNode(node, context);
}
else if (node instanceof NewLineNode) {
processNewLineNode(node, context);
}
}
function hasContent(node, ctx) {
if (typeof (node) === 'string') {
return node.length !== 0; // cs: do not ignore ws only content here, enclosed within other nodes it will matter!
}
else if (node instanceof CompositeGeneratorNode) {
return node.contents.some(e => hasContent(e, ctx));
}
else if (node instanceof NewLineNode) {
return !(node.ifNotEmpty && ctx.currentLineContent.length === 0);
}
else {
return false;
}
}
function processStringNode(node, context) {
if (node) {
handlePendingIndent(context, false);
context.append(node);
}
}
function handlePendingIndent(ctx, endOfLine) {
if (ctx.pendingIndent) {
let indent = '';
for (const indentNode of ctx.relevantIndents.filter(e => e.indentEmptyLines || !endOfLine)) {
indent += indentNode.indentation ?? ctx.defaultIndentation;
}
ctx.append(indent, true);
ctx.pendingIndent = false;
}
}
function processCompositeNode(node, context) {
let traceRegion = undefined;
const sourceRegion = getSourceRegion(node.tracedSource);
if (sourceRegion) {
traceRegion = context.pushTraceRegion(sourceRegion);
}
for (const child of node.contents) {
processNodeInternal(child, context);
}
if (traceRegion) {
context.popTraceRegion(traceRegion);
const parentsFileURI = context.getParentTraceSourceFileURI();
if (parentsFileURI && sourceRegion?.fileURI === parentsFileURI) {
// if some parent's sourceRegion refers to the same source file uri (and no other source file was referenced inbetween)
// we can drop the file uri in order to reduce repeated strings
delete sourceRegion.fileURI;
}
traceRegion.complete && traceRegion.complete(context.currentPosition);
}
}
function processIndentNode(node, context) {
if (hasContent(node, context)) {
if (node.indentImmediately && !context.pendingIndent) {
context.append(node.indentation ?? context.defaultIndentation, true);
}
try {
context.increaseIndent(node);
processCompositeNode(node, context);
}
finally {
context.decreaseIndent();
}
}
}
function processNewLineNode(node, context) {
if (node.ifNotEmpty && !hasNonWhitespace(context.currentLineContent)) {
context.resetCurrentLine();
}
else {
handlePendingIndent(context, true);
let count = node.count;
while (count-- > 0) {
context.append(node.lineDelimiter);
context.addNewLine();
}
}
}
function hasNonWhitespace(text) {
return text.trimStart() !== '';
}
//# sourceMappingURL=node-processor.js.map
File diff suppressed because one or more lines are too long
+280
View File
@@ -0,0 +1,280 @@
/******************************************************************************
* 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 type { AstNode, Properties } from '../syntax-tree.js';
import { CompositeGeneratorNode } from './generator-node.js';
import type { SourceRegion } from './generator-tracing.js';
/**
* A tag function that attaches the template's content to a {@link CompositeGeneratorNode}.
*
* This is done segment by segment, and static template portions as well as substitutions
* are added individually to the returned {@link CompositeGeneratorNode}.
* At that common leading indentation of all the template's static parts is trimmed,
* whereas additional indentations of particular lines within that static parts as well as
* any line breaks and indentation within the substitutions are kept.
*
* For the sake of good readability and good composability of results of this function like
* in the following example, the subsequent rule is applied.
*
* ```ts
* expandToNode`
* This is the beginning of something
*
* ${foo.bar ? expandToNode`
* bla bla bla ${foo.bar}
*
* `: undefined}
* end of something
* `
* ```
*
* Rule:
* In case of a multiline template
* 1. the content of the first line including its terminating line break is ignored,
* if and only if it is empty or contains whitespace only.
* 2. the content of the last line including its preceding line break (last line within the template excluding the trailing backtick) is ignored,
* if and only if it is empty or contains whitespace only, and the whitespace in-between the last line break and the trailing backtick
* is a real prefix of the common indentation among all none-empty lines of the template, i.e.,
* it is shorter than the common indentation that is trimmed during processing the template provided that common indentation is identifiable.
*
* Thus, the results of all of the following invocations are identical and equal to `generatedContent`.
* ```ts
* expandToNode`generatedContent`
* expandToNode`
* generatedContent`
* expandToNode`
* generatedContent
* `
* expandToNode`
* generatedContent
* `
* ```
* In contrast, the results of the following invocations are equal to `generatedContent\n` (or `generatedContent\r\n` on MS Windows)
* ```ts
* expandToNode`generatedContent
* `
* expandToNode`
* generatedContent
* `
* ```
*
* In addition, a third rule is applied while processing line breaks:
* If a line's last substitution contributes `undefined` or an object of type {@link GeneratorNode},
* the subsequent line break will be appended via {@link CompositeGeneratorNode.appendNewLineIfNotEmpty}.
* Hence, if all other segments of that line contribute whitespace characters only,
* the entire line will be omitted while rendering the desired output.
* Otherwise, linebreaks will be added via {@link CompositeGeneratorNode.appendNewLine}.
* That holds in particular, if the last substitution contributes an empty string. In consequence,
* adding `${''}` to the end of a line consisting of whitespace and substitutions only
* enforces the line break to be rendered, no matter what the substitutions actually contribute.
*
* @param staticParts the static parts of a tagged template literal
* @param substitutions the variable parts of a tagged template literal
* @returns a 'CompositeGeneratorNode' containing the particular aligned lines
* after resolving and inserting the substitutions into the given parts
*/
export declare function expandToNode(staticParts: TemplateStringsArray, ...substitutions: unknown[]): CompositeGeneratorNode;
/**
* Convenience function for creating a {@link CompositeGeneratorNode} being configured with the
* provided tracing information in form of `{astNode, property?, index: undefined}` and appending content
* in form of a template.
*
* This function returns a tag function that takes the desired template and does the processing
* by delegating to {@link expandToNode} and {@link traceToNode} and finally returning the
* resulting generator node.
*
* @param astNode the AstNode corresponding to the appended content
*
* @param property the value property name (string) corresponding to the appended content,
* if e.g. the content corresponds to some `string` or `number` property of `astNode`, is optional
*
* @returns a tag function behaving as described above, which in turn returns a {@link CompositeGeneratorNode}.
*
* @example
* expandTracedToNode(entity)`
* Hello ${ traceToNode(entity, 'name')(entity.name) }
* `.appendNewLine()
*/
export declare function expandTracedToNode<T extends AstNode>(astNode: T, property?: Properties<T>): (staticParts: TemplateStringsArray, ...substitutions: unknown[]) => CompositeGeneratorNode;
/**
* Convenience function for creating a {@link CompositeGeneratorNode} being configured with the
* provided tracing information in form of `{astNode, property, index}` and appending content
* in form of a template.
*
* This function returns a tag function that takes the desired template and does the processing
* by delegating to {@link expandToNode} and {@link traceToNode} and finally returning the
* resulting generator node.
*
* @param astNode the AstNode corresponding to the appended content
*
* @param property the value property name (string) corresponding to the appended content,
* if e.g. the content corresponds to some `string` or `number` property of `astNode`
*
* @param index the index of the value within a list property corresponding to the appended content,
* if the property contains a list of elements, is ignored otherwise
*
* @returns a tag function behaving as described above, which in turn returns a {@link CompositeGeneratorNode}.
*
* @example
* expandTracedToNode(entity, 'definitions', 0)`
* Hello ${ traceToNode(entity, 'name')(entity.name) }
* `.appendNewLine()
*/
export declare function expandTracedToNode<T extends AstNode>(astNode: T, property: Properties<T>, index?: number | undefined): (staticParts: TemplateStringsArray, ...substitutions: unknown[]) => CompositeGeneratorNode;
/**
* Convenience function for creating a {@link CompositeGeneratorNode} being configured with the
* provided tracing information in form of concrete coordinates and appending content
* in form of a template. Complete coordinates are provided by the {@link AstNode AstNodes}'
* corresponding {@link AstNode.$cstNode AstNode.$cstNodes}.
*
* This function returns a tag function that takes the desired template and does the processing
* by delegating to {@link expandToNode} and {@link traceToNode} and finally returning the
* resulting generator node.
*
* @param sourceRegion a text region within some file in form of concrete coordinates,
* if `undefined` no tracing will happen
*
* @returns a tag function behaving as described above, which in turn returns a {@link CompositeGeneratorNode}.
*
* @example
* expandTracedToNode(entity.$cstNode)`
* Hello ${ traceToNode(entity, 'name')(entity.name) }
* `.appendNewLine()
*/
export declare function expandTracedToNode(sourceRegion: SourceRegion | undefined): (staticParts: TemplateStringsArray, ...substitutions: unknown[]) => CompositeGeneratorNode;
/**
* Convenience function for creating a {@link CompositeGeneratorNode} being configured with the
* provided tracing information in form of a list of concrete coordinates and appending content
* in form of a template. Complete coordinates are provided by the {@link AstNode AstNodes}'
* corresponding {@link AstNode.$cstNode AstNode.$cstNodes}.
*
* This function returns a tag function that takes the desired template and does the processing
* by delegating to {@link expandToNode} and {@link traceToNode} and finally returning the
* resulting generator node.
*
* The list of regions in `sourceRegions` will later be reduced to the smallest encompassing region
* of all the contained source regions.
*
* @param sourceRegions a list of text regions within some file in form of concrete coordinates,
* if empty no tracing will happen
*
* @returns a tag function behaving as described above, which in turn returns a {@link CompositeGeneratorNode}.
*
* @example
* expandTracedToNode([
* findNodeForKeyword(entity.$cstNode, '{')!,
* findNodeForKeyword(entity.$cstNode, '}')!
* ])`
* Hello ${ traceToNode(entity, 'name')(entity.name) }
* `.appendNewLine()
*/
export declare function expandTracedToNode(sourceRegions: SourceRegion[]): (staticParts: TemplateStringsArray, ...substitutions: unknown[]) => CompositeGeneratorNode;
/**
* Convenience function for creating a {@link CompositeGeneratorNode} being configured with the
* provided tracing information in form of `{astNode, property?, index: undefined}` and appending content
* in form of a template, if `condition` is equal to `true`.
*
* If `condition` is satisfied, this function returns a tag function that takes the desired template
* and does the processing by delegating to {@link expandToNode} and {@link traceToNode} and
* finally returning the resulting generator node. Otherwise, the returned function just returns `undefined`.
*
* @param condition a boolean value indicating whether to evaluate the provided template.
*
* @param astNode the AstNode corresponding to the appended content
*
* @param property the value property name (string) corresponding to the appended content,
* if e.g. the content corresponds to some `string` or `number` property of `astNode`, is optional
*
* @returns a tag function behaving as described above, which in turn returns a {@link CompositeGeneratorNode}.
*
* @example
* expandTracedToNodeIf(entity !== undefined, entity)`
* Hello ${ traceToNode(entity, 'name')(entity.name) }
* `.appendNewLine()
*/
export declare function expandTracedToNodeIf<T extends AstNode>(condition: boolean, astNode: T, property?: Properties<T>): (staticParts: TemplateStringsArray, ...substitutions: unknown[]) => CompositeGeneratorNode | undefined;
/**
* Convenience function for creating a {@link CompositeGeneratorNode} being configured with the
* provided tracing information in form of `{astNode, property, index}` and appending content
* in form of a template, if `condition` is equal to `true`.
*
* If `condition` is satisfied, this function returns a tag function that takes the desired template
* and does the processing by delegating to {@link expandToNode} and {@link traceToNode} and
* finally returning the resulting generator node. Otherwise, the returned function just returns `undefined`.
*
* @param condition a boolean value indicating whether to evaluate the provided template.
*
* @param astNode the AstNode corresponding to the appended content
*
* @param property the value property name (string) corresponding to the appended content,
* if e.g. the content corresponds to some `string` or `number` property of `astNode`
*
* @param index the index of the value within a list property corresponding to the appended content,
* if the property contains a list of elements, is ignored otherwise
*
* @returns a tag function behaving as described above, which in turn returns a {@link CompositeGeneratorNode}.
*
* @example
* expandTracedToNodeIf(entity !== undefined, entity, 'definitions', 0)`
* Hello ${ traceToNode(entity, 'name')(entity.name) }
* `.appendNewLine()
*/
export declare function expandTracedToNodeIf<T extends AstNode>(condition: boolean, astNode: T, property: Properties<T>, index: number | undefined): (staticParts: TemplateStringsArray, ...substitutions: unknown[]) => CompositeGeneratorNode | undefined;
/**
* Convenience function for creating a {@link CompositeGeneratorNode} being configured with the
* provided tracing information in form of concrete coordinates and appending content in form
* of a template, if `condition` is equal to `true`. Complete coordinates are provided by
* the {@link AstNode AstNodes}' corresponding {@link AstNode.$cstNode AstNode.$cstNodes}.
*
* This function returns a tag function that takes the desired template and does the processing
* by delegating to {@link expandToNode} and {@link traceToNode} and finally returning the
* resulting generator node.
*
* If `sourceRegion` is a function supplying the corresponding region, it's only called if `condition` is satisfied.
*
* @param condition a boolean value indicating whether to evaluate the provided template.
*
* @param sourceRegion a text region within some file in form of concrete coordinates or a supplier function,
* if `undefined` no tracing will happen
*
* @returns a tag function behaving as described above, which in turn returns a {@link CompositeGeneratorNode}.
*
* @example
* expandTracedToNodeIf(entity !== undefined, entity.$cstNode)`
* Hello ${ traceToNode(entity, 'name')(entity.name) }
* `.appendNewLine()
*/
export declare function expandTracedToNodeIf(condition: boolean, sourceRegion: SourceRegion | undefined | (() => SourceRegion | undefined)): (staticParts: TemplateStringsArray, ...substitutions: unknown[]) => CompositeGeneratorNode | undefined;
/**
* Convenience function for creating a {@link CompositeGeneratorNode} being configured with the
* provided tracing information in form of a list of concrete coordinates and appending content
* in form of a template, if `condition` is equal to `true`. Complete coordinates are provided
* by the {@link AstNode AstNodes}' corresponding {@link AstNode.$cstNode AstNode.$cstNodes}.
*
* This function returns a tag function that takes the desired template and does the processing
* by delegating to {@link expandToNode} and {@link traceToNode} and finally returning the
* resulting generator node.
*
* The list of regions in `sourceRegions` will later be reduced to the smallest encompassing region
* of all the contained source regions.
* If `sourceRegions` is a function supplying the corresponding regions, it's only called if `condition` is satisfied.
*
* @param condition a boolean value indicating whether to evaluate the provided template.
*
* @param sourceRegions a list of text regions within some file in form of concrete coordinates,
* if empty no tracing will happen
*
* @returns a tag function behaving as described above, which in turn returns a {@link CompositeGeneratorNode}.
*
* @example
* expandTracedToNodeIf(entity !== undefined, [
* findNodeForKeyword(entity.$cstNode, '{')!,
* findNodeForKeyword(entity.$cstNode, '}')!
* ])`
* Hello ${ traceToNode(entity, 'name')(entity.name) }
* `.appendNewLine()
*/
export declare function expandTracedToNodeIf(condition: boolean, sourceRegions: SourceRegion[]): (staticParts: TemplateStringsArray, ...substitutions: unknown[]) => CompositeGeneratorNode | undefined;
//# sourceMappingURL=template-node.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"template-node.d.ts","sourceRoot":"","sources":["../../src/generate/template-node.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAEhF,OAAO,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAG7D,OAAO,EAAE,sBAAsB,EAAgC,MAAM,qBAAqB,CAAC;AAC3F,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAG3D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoEG;AACH,wBAAgB,YAAY,CAAC,WAAW,EAAE,oBAAoB,EAAE,GAAG,aAAa,EAAE,OAAO,EAAE,GAAG,sBAAsB,CAUnH;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,QAAQ,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,WAAW,EAAE,oBAAoB,EAAE,GAAG,aAAa,EAAE,OAAO,EAAE,KAAK,sBAAsB,CAAC;AAExL;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,CAAC,WAAW,EAAE,oBAAoB,EAAE,GAAG,aAAa,EAAE,OAAO,EAAE,KAAK,sBAAsB,CAAC;AAEnN;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,kBAAkB,CAAC,YAAY,EAAE,YAAY,GAAG,SAAS,GAAG,CAAC,WAAW,EAAE,oBAAoB,EAAE,GAAG,aAAa,EAAE,OAAO,EAAE,KAAK,sBAAsB,CAAC;AAEvK;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,kBAAkB,CAAC,aAAa,EAAE,YAAY,EAAE,GAAG,CAAC,WAAW,EAAE,oBAAoB,EAAE,GAAG,aAAa,EAAE,OAAO,EAAE,KAAK,sBAAsB,CAAC;AAW9J;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,SAAS,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,QAAQ,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,GACxG,CAAC,WAAW,EAAE,oBAAoB,EAAE,GAAG,aAAa,EAAE,OAAO,EAAE,KAAK,sBAAsB,GAAG,SAAS,CAAC;AAE/G;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,SAAS,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,GAClI,CAAC,WAAW,EAAE,oBAAoB,EAAE,GAAG,aAAa,EAAE,OAAO,EAAE,KAAK,sBAAsB,GAAG,SAAS,CAAC;AAE/G;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,OAAO,EAAE,YAAY,EAAE,YAAY,GAAG,SAAS,GAAG,CAAC,MAAM,YAAY,GAAG,SAAS,CAAC,GAC1H,CAAC,WAAW,EAAE,oBAAoB,EAAE,GAAG,aAAa,EAAE,OAAO,EAAE,KAAK,sBAAsB,GAAG,SAAS,CAAC;AAE/G;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,GAC9E,CAAC,WAAW,EAAE,oBAAoB,EAAE,GAAG,aAAa,EAAE,OAAO,EAAE,KAAK,sBAAsB,GAAG,SAAS,CAAC"}
+301
View File
@@ -0,0 +1,301 @@
/******************************************************************************
* 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 { NEWLINE_REGEXP } from '../utils/regexp-utils.js';
import { CompositeGeneratorNode, isGeneratorNode, traceToNode } from './generator-node.js';
import { findIndentation } from './template-string.js';
/**
* A tag function that attaches the template's content to a {@link CompositeGeneratorNode}.
*
* This is done segment by segment, and static template portions as well as substitutions
* are added individually to the returned {@link CompositeGeneratorNode}.
* At that common leading indentation of all the template's static parts is trimmed,
* whereas additional indentations of particular lines within that static parts as well as
* any line breaks and indentation within the substitutions are kept.
*
* For the sake of good readability and good composability of results of this function like
* in the following example, the subsequent rule is applied.
*
* ```ts
* expandToNode`
* This is the beginning of something
*
* ${foo.bar ? expandToNode`
* bla bla bla ${foo.bar}
*
* `: undefined}
* end of something
* `
* ```
*
* Rule:
* In case of a multiline template
* 1. the content of the first line including its terminating line break is ignored,
* if and only if it is empty or contains whitespace only.
* 2. the content of the last line including its preceding line break (last line within the template excluding the trailing backtick) is ignored,
* if and only if it is empty or contains whitespace only, and the whitespace in-between the last line break and the trailing backtick
* is a real prefix of the common indentation among all none-empty lines of the template, i.e.,
* it is shorter than the common indentation that is trimmed during processing the template provided that common indentation is identifiable.
*
* Thus, the results of all of the following invocations are identical and equal to `generatedContent`.
* ```ts
* expandToNode`generatedContent`
* expandToNode`
* generatedContent`
* expandToNode`
* generatedContent
* `
* expandToNode`
* generatedContent
* `
* ```
* In contrast, the results of the following invocations are equal to `generatedContent\n` (or `generatedContent\r\n` on MS Windows)
* ```ts
* expandToNode`generatedContent
* `
* expandToNode`
* generatedContent
* `
* ```
*
* In addition, a third rule is applied while processing line breaks:
* If a line's last substitution contributes `undefined` or an object of type {@link GeneratorNode},
* the subsequent line break will be appended via {@link CompositeGeneratorNode.appendNewLineIfNotEmpty}.
* Hence, if all other segments of that line contribute whitespace characters only,
* the entire line will be omitted while rendering the desired output.
* Otherwise, linebreaks will be added via {@link CompositeGeneratorNode.appendNewLine}.
* That holds in particular, if the last substitution contributes an empty string. In consequence,
* adding `${''}` to the end of a line consisting of whitespace and substitutions only
* enforces the line break to be rendered, no matter what the substitutions actually contribute.
*
* @param staticParts the static parts of a tagged template literal
* @param substitutions the variable parts of a tagged template literal
* @returns a 'CompositeGeneratorNode' containing the particular aligned lines
* after resolving and inserting the substitutions into the given parts
*/
export function expandToNode(staticParts, ...substitutions) {
// first part: determine the common indentation of all the template lines with the substitutions being ignored
const templateProps = findIndentationAndTemplateStructure(staticParts);
// 2nd part: for all the static template parts: split them and inject a NEW_LINE marker where line breaks shall be a present in the final result,
// and create a flatten list of strings, NEW_LINE marker occurrences, and substitutions
const splitAndMerged = splitTemplateLinesAndMergeWithSubstitutions(staticParts, substitutions, templateProps);
// eventually, inject indentation nodes and append the segments to final desired composite generator node
return composeFinalGeneratorNode(splitAndMerged);
}
// implementation:
export function expandTracedToNode(source, property, index) {
return (staticParts, ...substitutions) => {
return traceToNode(source, property, index)(expandToNode(staticParts, ...substitutions));
};
}
// implementation:
export function expandTracedToNodeIf(condition, source, property, index) {
return condition ? expandTracedToNode((typeof source === 'function' ? source() : source), property, index) : () => undefined;
}
function findIndentationAndTemplateStructure(staticParts) {
const lines = staticParts.join('_').split(NEWLINE_REGEXP);
const omitFirstLine = lines.length > 1 && lines[0].trim().length === 0;
const omitLastLine = omitFirstLine && lines.length > 1 && lines[lines.length - 1].trim().length === 0;
if (lines.length === 1 || lines.length !== 0 && lines[0].trim().length !== 0 || lines.length === 2 && lines[1].trim().length === 0) {
// for cases of non-adjusted templates like
// const n1 = expandToNode` `;
// const n2 = expandToNode` something `;
// const n3 = expandToNode` something
// `;
// ... consider the indentation to be empty, and all the leading whitespace to be relevant, except for the last (empty) line of n3!
return {
indentation: 0, //''
omitFirstLine,
omitLastLine,
trimLastLine: lines.length !== 1 && lines[lines.length - 1].trim().length === 0
};
}
else {
// otherwise:
// for cases of non-adjusted templates like
// const n4 = expandToNode` abc
// def `;
// const n5 = expandToNode`<maybe with some WS here>
// abc
// def`;
// const n6 = expandToNode`<maybe with some WS here>
// abc
// def
// `;
// ... the indentation shall be determined by the non-empty lines, excluding the last line if it contains whitespace only
// if we have a multi-line template and the first line is empty, see n5, n6
// ignore the first line;
let sliced = omitFirstLine ? lines.slice(1) : lines;
// if there're more than one line remaining and the last one only contains WS, see n6,
// ignore the last line
sliced = omitLastLine ? sliced.slice(0, sliced.length - 1) : sliced;
// ignore empty lines during indentation calculation, as linting rules might forbid lines containing just whitespace
sliced = sliced.filter(e => e.length !== 0);
const indentation = findIndentation(sliced);
return {
indentation,
omitFirstLine,
// in the subsequent steps omit the last line only if it is empty or if it only contains whitespace of which the common indentation is not a valid prefix;
// in other words: keep the last line if it matches the common indentation (and maybe contains non-whitespace), a non-match may be due to mistaken usage of tabs and spaces
omitLastLine: omitLastLine && (lines[lines.length - 1].length < indentation || !lines[lines.length - 1].startsWith(sliced[0].substring(0, indentation)))
};
}
}
function splitTemplateLinesAndMergeWithSubstitutions(staticParts, substitutions, { indentation, omitFirstLine, omitLastLine, trimLastLine }) {
const splitAndMerged = [];
staticParts.forEach((part, i) => {
splitAndMerged.push(...part.split(NEWLINE_REGEXP).map((e, j) => j === 0 || e.length < indentation ? e : e.substring(indentation)).reduce(
// treat the particular (potentially multiple) lines of the <i>th template segment (part),
// s.t. all the effective lines are collected and separated by the NEWLINE node
// note: different reduce functions are provided for the initial template segment vs. the remaining segments
i === 0
? (result, line, j) =>
// special handling of the initial template segment, which may contain line-breaks;
// suppresses the injection of unintended NEWLINE indicators for templates like
// expandToNode`
// someText
// ${something}
// `
j === 0
? (omitFirstLine // for templates with empty first lines like above (expandToNode`\n ...`)
? [] // skip adding the initial line
: [line] // take the initial line if non-empty
)
: (j === 1 && result.length === 0 // when looking on the 2nd line in case the first line (in the first segment) is skipped ('result' is still empty)
? [line] // skip the insertion of the NEWLINE marker and just return the current line
: result.concat(NEWLINE, line) // otherwise append the NEWLINE marker and the current line
)
: (result, line, j) =>
// handling of the remaining template segments
j === 0 ? [line] : result.concat(NEWLINE, line) // except for the first line in the current segment prepend each line with NEWLINE
, [] // start with an empty array
).filter(e => !(typeof e === 'string' && e.length === 0) // drop empty strings, they don't contribute anything but might confuse subsequent processing
).concat(
// append the corresponding substitution after each segment (part),
// note that 'substitutions[i]' will be undefined for the last segment
isGeneratorNode(substitutions[i])
// if the substitution is a generator node, take it as it is
? substitutions[i]
: substitutions[i] !== undefined
// if the substitution is something else, convert it to a string and wrap it;
// allows us below to distinguish template strings from substitution (esp. empty) ones
? { content: String(substitutions[i]) }
: i < substitutions.length
// if 'substitutions[i]' is undefined and we are treating a substitution "in the middle"
// we found a substitution that is assumed to not contribute anything on purpose!
? UNDEFINED_SEGMENT // add a corresponding marker, see below for details on the rational
: [] /* don't concat anything as we passed behind the last substitution, since 'i' enumerates the indices of 'staticParts',
but 'substitutions' has one entry less and 'substitutions[staticParts.length -1 ]' will always be undefined */));
});
// for templates like
// expandToNode`
// someText
// `
// TODO add more documentation here
const splitAndMergedLength = splitAndMerged.length;
const lastItem = splitAndMergedLength !== 0 ? splitAndMerged[splitAndMergedLength - 1] : undefined;
if ((omitLastLine || trimLastLine) && typeof lastItem === 'string' && lastItem.trim().length === 0) {
if (omitFirstLine && splitAndMergedLength !== 1 && splitAndMerged[splitAndMergedLength - 2] === NEWLINE) {
return splitAndMerged.slice(0, splitAndMergedLength - 2);
}
else {
return splitAndMerged.slice(0, splitAndMergedLength - 1);
}
}
else {
return splitAndMerged;
}
}
const NEWLINE = { isNewLine: true };
const UNDEFINED_SEGMENT = { isUndefinedSegment: true };
const isNewLineMarker = (nl) => nl === NEWLINE;
const isUndefinedSegmentMarker = (us) => us === UNDEFINED_SEGMENT;
const isSubstitutionWrapper = (s) => s.content !== undefined;
function composeFinalGeneratorNode(splitAndMerged) {
// in order to properly handle the indentation of nested multi-line substitutions,
// track the length of static (string) parts per line and wrap the substitution(s) in indentation nodes, if needed
//
// of course, this only works nicely if a multi-line substitution is preceded by static string parts on the same line only;
// in case of dynamic content (with a potentially unknown length) followed by a multi-line substitution
// the latter's indentation cannot be determined properly...
const result = splitAndMerged.reduce((res, segment, i) => isUndefinedSegmentMarker(segment)
// ignore all occurrences of UNDEFINED_SEGMENT, they are just in there for the below test
// of 'isNewLineMarker(splitAndMerged[i-1])' not to evaluate to 'truthy' in case of consecutive lines
// with no actual content in templates like
// expandToNode`
// Foo
// ${undefined} <<----- here
// ${undefined} <<----- and here
//
// Bar
// `
? res
: isNewLineMarker(segment)
? {
// in case of a newLine marker append an 'ifNotEmpty' newLine by default, but
// append an unconditional newLine if and only if:
// * the template starts with the current line break, i.e. the first line is empty
// * the current newLine marker directly follows another one, i.e. the current line is empty
// * the current newline marker directly follows a substitution contributing a string (or some non-GeneratorNode being converted to a string)
// * the current newline marker directly follows a (template static) string that
// * is the initial token of the template
// * is the initial token of the line, maybe just indentation
// * follows a a substitution contributing a string (or some non-GeneratorNode being converted to a string), maybe is just irrelevant trailing whitespace
// in particular do _not_ append an unconditional newLine if the last substitution of a line contributes 'undefined' or an instance of 'GeneratorNode'
// which may be a newline itself or be empty or (transitively) contain a trailing newline itself
// node: i === 0
// || isNewLineMarker(splitAndMerged[i - 1]) || isSubstitutionWrapper(splitAndMerged[i - 1]) /* implies: typeof content === 'string', esp. !undefined */
// || typeof splitAndMerged[i - 1] === 'string' && (
// i === 1 || isNewLineMarker(splitAndMerged[i - 2]) || isSubstitutionWrapper(splitAndMerged[i - 2]) /* implies: typeof content === 'string', esp. !undefined */
// )
// ? res.node.appendNewLine() : res.node.appendNewLineIfNotEmpty()
//
// UPDATE cs: inverting the logic leads to the following, I hope I didn't miss anything:
// in case of a newLine marker append an unconditional newLine by default, but
// append an 'ifNotEmpty' newLine if and only if:
// * the template doesn't start with a newLine marker and
// * the current newline marker directly follows a substitution contributing an `undefined` or an instance of 'GeneratorNode', or
// * the current newline marker directly follows a (template static) string (containing potentially unintended trailing whitespace)
// that in turn directly follows a substitution contributing an `undefined` or an instance of 'GeneratorNode'
node: i !== 0 && (isUndefinedSegmentMarker(splitAndMerged[i - 1]) || isGeneratorNode(splitAndMerged[i - 1]))
|| i > 1 && typeof splitAndMerged[i - 1] === 'string' && (isUndefinedSegmentMarker(splitAndMerged[i - 2]) || isGeneratorNode(splitAndMerged[i - 2]))
? res.node.appendNewLineIfNotEmpty() : res.node.appendNewLine()
} : (() => {
// the indentation handling is supposed to handle use cases like
// bla bla bla {
// ${foo(bar)}
// }
// and
// bla bla bla {
// return ${foo(bar)}
// }
// assuming that ${foo(bar)} yields a multiline result;
// the whitespace between 'return' and '${foo(bar)}' shall not add to the indentation of '${foo(bar)}'s result!
const indent = (i === 0 || isNewLineMarker(splitAndMerged[i - 1])) && typeof segment === 'string' && segment.length !== 0 ? segment.substring(0, segment.length - segment.trimStart().length) : '';
const content = isSubstitutionWrapper(segment) ? segment.content : segment;
let indented;
return {
node: res.indented
// in case an indentNode has been registered earlier for the current line,
// just return 'node' without manipulation, the current segment will be added to the indentNode
? res.node
// otherwise (no indentNode is registered by now)...
: indent.length !== 0
// in case an indentation has been identified add a non-immediate indentNode to 'node' and
// add the current segment (containing its the indentation) to that indentNode,
// and keep the indentNode in a local variable 'indented' for registering below,
// and return 'node'
? res.node.indent({ indentation: indent, indentImmediately: false, indentedChildren: ind => indented = ind.append(content) })
// otherwise just add the content to 'node' and return it
: res.node.append(content),
indented:
// if an indentNode has been created in this cycle, just register it,
// otherwise check for a earlier registered indentNode and add the current segment to that one
indented ?? res.indented?.append(content),
};
})(), { node: new CompositeGeneratorNode() });
return result.node;
}
//# sourceMappingURL=template-node.js.map
File diff suppressed because one or more lines are too long
+29
View File
@@ -0,0 +1,29 @@
/******************************************************************************
* 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 function expandToStringWithNL(staticParts: TemplateStringsArray, ...substitutions: unknown[]): string;
export declare function expandToStringLFWithNL(staticParts: TemplateStringsArray, ...substitutions: unknown[]): string;
/**
* A tag function that automatically aligns embedded multiline strings.
* Multiple lines are joined with the platform-specific line separator.
*
* @param staticParts the static parts of a tagged template literal
* @param substitutions the variable parts of a tagged template literal
* @returns an aligned string that consists of the given parts
*/
export declare function expandToString(staticParts: TemplateStringsArray, ...substitutions: unknown[]): string;
/**
* A tag function that automatically aligns embedded multiline strings.
* Multiple lines are joined with the LINE_FEED (`\n`) line separator.
*
* @param staticParts the static parts of a tagged template literal
* @param substitutions the variable parts of a tagged template literal
* @returns an aligned string that consists of the given parts
*/
export declare function expandToStringLF(staticParts: TemplateStringsArray, ...substitutions: unknown[]): string;
export declare const SNLE: string;
export declare function findIndentation(lines: string[]): number;
export declare function normalizeEOL(input: string): string;
//# sourceMappingURL=template-string.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"template-string.d.ts","sourceRoot":"","sources":["../../src/generate/template-string.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAKhF,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,oBAAoB,EAAE,GAAG,aAAa,EAAE,OAAO,EAAE,GAAG,MAAM,CAE3G;AAED,wBAAgB,sBAAsB,CAAC,WAAW,EAAE,oBAAoB,EAAE,GAAG,aAAa,EAAE,OAAO,EAAE,GAAG,MAAM,CAE7G;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,WAAW,EAAE,oBAAoB,EAAE,GAAG,aAAa,EAAE,OAAO,EAAE,GAAG,MAAM,CAErG;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,oBAAoB,EAAE,GAAG,aAAa,EAAE,OAAO,EAAE,GAAG,MAAM,CAEvG;AAyCD,eAAO,MAAM,IAAI,QAAgD,CAAC;AAWlE,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,CAIvD;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAElD"}
+87
View File
@@ -0,0 +1,87 @@
/******************************************************************************
* 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 { NEWLINE_REGEXP } from '../utils/regexp-utils.js';
import { EOL, toString } from './generator-node.js';
export function expandToStringWithNL(staticParts, ...substitutions) {
return expandToString(staticParts, ...substitutions) + EOL;
}
export function expandToStringLFWithNL(staticParts, ...substitutions) {
return expandToStringLF(staticParts, ...substitutions) + '\n';
}
/**
* A tag function that automatically aligns embedded multiline strings.
* Multiple lines are joined with the platform-specific line separator.
*
* @param staticParts the static parts of a tagged template literal
* @param substitutions the variable parts of a tagged template literal
* @returns an aligned string that consists of the given parts
*/
export function expandToString(staticParts, ...substitutions) {
return internalExpandToString(EOL, staticParts, ...substitutions);
}
/**
* A tag function that automatically aligns embedded multiline strings.
* Multiple lines are joined with the LINE_FEED (`\n`) line separator.
*
* @param staticParts the static parts of a tagged template literal
* @param substitutions the variable parts of a tagged template literal
* @returns an aligned string that consists of the given parts
*/
export function expandToStringLF(staticParts, ...substitutions) {
return internalExpandToString('\n', staticParts, ...substitutions);
}
function internalExpandToString(lineSep, staticParts, ...substitutions) {
let lines = substitutions
// align substitutions and fuse them with static parts
.reduce((acc, subst, i) => acc + (subst === undefined ? SNLE : align(toString(subst), acc)) + (staticParts[i + 1] ?? ''), staticParts[0])
// converts text to lines
.split(NEWLINE_REGEXP)
.filter(l => l.trim() !== SNLE)
// whitespace-only lines are empty (preserving leading whitespace)
.map(l => l.replace(SNLE, '').trimEnd());
// in order to nicely handle single line templates with the leading and trailing termintators (``) on separate lines, like
// expandToString`foo
// `,
// expandToString`
// foo
// `,
// expandToString`
// foo`,
// the same way as true single line templates like
// expandToString`foo`
// ...
// ... drop initial linebreak if the first line is empty or contains white space only, ...
const containsLeadingLinebreak = lines.length > 1 && lines[0].trim().length === 0;
lines = containsLeadingLinebreak ? lines.slice(1) : lines;
// .. and drop the last linebreak if it's the last charactor or is followed by white space
const containsTrailingLinebreak = lines.length !== 0 && lines[lines.length - 1].trimEnd().length === 0;
lines = containsTrailingLinebreak ? lines.slice(0, lines.length - 1) : lines;
// finds the minimum indentation
const indent = findIndentation(lines);
return lines
// shifts lines to the left
.map(line => line.slice(indent).trimEnd())
// convert lines to string
.join(lineSep);
}
export const SNLE = Object.freeze('__«SKIP^NEW^LINE^IF^EMPTY»__');
const nonWhitespace = /\S|$/;
// add the alignment of the previous static part to all lines of the following substitution
function align(subst, acc) {
const length = Math.max(0, acc.length - acc.lastIndexOf('\n') - 1);
const indent = ' '.repeat(length);
return subst.replace(NEWLINE_REGEXP, EOL + indent);
}
// finds the indentation of a text block represented by a sequence of lines
export function findIndentation(lines) {
const indents = lines.filter(line => line.length > 0).map(line => line.search(nonWhitespace));
const min = indents.length === 0 ? 0 : Math.min(...indents); // min(...[]) = min() = Infinity
return Math.max(0, min);
}
export function normalizeEOL(input) {
return input.replace(NEWLINE_REGEXP, EOL);
}
//# sourceMappingURL=template-string.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"template-string.js","sourceRoot":"","sources":["../../src/generate/template-string.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
/******************************************************************************
* 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 type { AstReflection } from '../syntax-tree.js';
import type { LangiumCoreServices } from '../index.js';
import type { Grammar } from '../languages/generated/ast.js';
import type { AstTypes } from './type-system/type-collector/types.js';
export declare function interpretAstReflection(astTypes: AstTypes): AstReflection;
export declare function interpretAstReflection(grammar: Grammar, services?: LangiumCoreServices): AstReflection;
//# sourceMappingURL=ast-reflection-interpreter.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"ast-reflection-interpreter.d.ts","sourceRoot":"","sources":["../../src/grammar/ast-reflection-interpreter.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAEhF,OAAO,KAAK,EAAE,aAAa,EAAkC,MAAM,mBAAmB,CAAC;AACvF,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AACvD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,+BAA+B,CAAC;AAC7D,OAAO,KAAK,EAAE,QAAQ,EAAY,MAAM,uCAAuC,CAAC;AAOhF,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,QAAQ,GAAG,aAAa,CAAC;AAC1E,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,mBAAmB,GAAG,aAAa,CAAC"}
+127
View File
@@ -0,0 +1,127 @@
/******************************************************************************
* 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 { AbstractAstReflection } from '../syntax-tree.js';
import { MultiMap } from '../utils/collections.js';
import { isGrammar } from '../languages/generated/ast.js';
import { collectAst } from './type-system/ast-collector.js';
import { collectTypeHierarchy, findReferenceTypes, isAstType, mergeTypesAndInterfaces } from './type-system/types-util.js';
export function interpretAstReflection(grammarOrTypes, services) {
let collectedTypes;
if (isGrammar(grammarOrTypes)) {
collectedTypes = collectAst(grammarOrTypes, { services });
}
else {
collectedTypes = grammarOrTypes;
}
const allTypes = collectedTypes.interfaces.map(e => e.name).concat(collectedTypes.unions.filter(e => isAstType(e.type)).map(e => e.name));
const references = buildReferenceTypes(collectedTypes);
const metaData = buildTypeMetaData(collectedTypes);
const superTypes = collectTypeHierarchy(mergeTypesAndInterfaces(collectedTypes)).superTypes;
return new InterpretedAstReflection({
allTypes,
references,
metaData,
superTypes
});
}
class InterpretedAstReflection extends AbstractAstReflection {
constructor(options) {
// Build the types object required by AbstractAstReflection
const types = {};
for (const typeName of options.allTypes) {
const typeMetaData = options.metaData.get(typeName);
if (typeMetaData) {
const properties = {};
// Convert properties array to object and add reference types
if (Array.isArray(typeMetaData.properties)) {
for (const prop of typeMetaData.properties) {
const referenceKey = `${typeName}:${prop.name}`;
const referenceType = options.references.get(referenceKey);
properties[prop.name] = {
name: prop.name,
defaultValue: prop.defaultValue,
...(referenceType && { referenceType })
};
}
}
else {
// If properties is already an object, copy it and add reference types
for (const [propName, prop] of Object.entries(typeMetaData.properties)) {
const referenceKey = `${typeName}:${propName}`;
const referenceType = options.references.get(referenceKey);
properties[propName] = {
...prop,
...(referenceType && { referenceType })
};
}
}
types[typeName] = {
name: typeName,
properties,
superTypes: Array.from(options.superTypes.get(typeName))
};
}
}
super();
// Initialize the readonly types field
Object.defineProperty(this, 'types', { value: types });
}
computeIsSubtype(subtype, originalSuperType) {
const typeMetaData = this.types[subtype];
if (!typeMetaData) {
return false;
}
for (const superType of typeMetaData.superTypes) {
if (this.isSubtype(superType, originalSuperType)) {
return true;
}
}
return false;
}
}
function buildReferenceTypes(astTypes) {
const references = new MultiMap();
for (const interfaceType of astTypes.interfaces) {
for (const property of interfaceType.properties) {
for (const referenceType of findReferenceTypes(property.type)) {
references.add(interfaceType.name, [property.name, referenceType]);
}
}
for (const superType of interfaceType.interfaceSuperTypes) {
const superTypeReferences = references.get(superType.name);
references.addAll(interfaceType.name, superTypeReferences);
}
}
const map = new Map();
for (const [type, [property, target]] of references) {
map.set(`${type}:${property}`, target);
}
return map;
}
function buildTypeMetaData(astTypes) {
const map = new Map();
for (const interfaceType of astTypes.interfaces) {
const props = interfaceType.superProperties;
map.set(interfaceType.name, {
name: interfaceType.name,
properties: buildPropertyMetaData(props),
superTypes: [] // Will be populated later from superTypes data
});
}
return map;
}
function buildPropertyMetaData(props) {
const properties = {};
const all = props.sort((a, b) => a.name.localeCompare(b.name));
for (const property of all) {
properties[property.name] = {
name: property.name,
defaultValue: property.defaultValue
};
}
return properties;
}
//# sourceMappingURL=ast-reflection-interpreter.js.map
@@ -0,0 +1 @@
{"version":3,"file":"ast-reflection-interpreter.js","sourceRoot":"","sources":["../../src/grammar/ast-reflection-interpreter.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAMhF,OAAO,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AACnD,OAAO,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AAC5D,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,SAAS,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAC;AAI3H,MAAM,UAAU,sBAAsB,CAAC,cAAkC,EAAE,QAA8B;IACrG,IAAI,cAAwB,CAAC;IAC7B,IAAI,SAAS,CAAC,cAAc,CAAC,EAAE,CAAC;QAC5B,cAAc,GAAG,UAAU,CAAC,cAAc,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC;IAC9D,CAAC;SAAM,CAAC;QACJ,cAAc,GAAG,cAAc,CAAC;IACpC,CAAC;IACD,MAAM,QAAQ,GAAG,cAAc,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,cAAc,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IAC1I,MAAM,UAAU,GAAG,mBAAmB,CAAC,cAAc,CAAC,CAAC;IACvD,MAAM,QAAQ,GAAG,iBAAiB,CAAC,cAAc,CAAC,CAAC;IACnD,MAAM,UAAU,GAAG,oBAAoB,CAAC,uBAAuB,CAAC,cAAc,CAAC,CAAC,CAAC,UAAU,CAAC;IAE5F,OAAO,IAAI,wBAAwB,CAAC;QAChC,QAAQ;QACR,UAAU;QACV,QAAQ;QACR,UAAU;KACb,CAAC,CAAC;AACP,CAAC;AAED,MAAM,wBAAyB,SAAQ,qBAAqB;IAExD,YAAY,OAKX;QACG,2DAA2D;QAC3D,MAAM,KAAK,GAAqC,EAAE,CAAC;QAEnD,KAAK,MAAM,QAAQ,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;YACtC,MAAM,YAAY,GAAG,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YACpD,IAAI,YAAY,EAAE,CAAC;gBACf,MAAM,UAAU,GAAyC,EAAE,CAAC;gBAE5D,6DAA6D;gBAC7D,IAAI,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC,UAAU,CAAC,EAAE,CAAC;oBACzC,KAAK,MAAM,IAAI,IAAI,YAAY,CAAC,UAAU,EAAE,CAAC;wBACzC,MAAM,YAAY,GAAG,GAAG,QAAQ,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;wBAChD,MAAM,aAAa,GAAG,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;wBAE3D,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;4BACpB,IAAI,EAAE,IAAI,CAAC,IAAI;4BACf,YAAY,EAAE,IAAI,CAAC,YAAY;4BAC/B,GAAG,CAAC,aAAa,IAAI,EAAE,aAAa,EAAE,CAAC;yBAC1C,CAAC;oBACN,CAAC;gBACL,CAAC;qBAAM,CAAC;oBACJ,sEAAsE;oBACtE,KAAK,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,UAAU,CAAC,EAAE,CAAC;wBACrE,MAAM,YAAY,GAAG,GAAG,QAAQ,IAAI,QAAQ,EAAE,CAAC;wBAC/C,MAAM,aAAa,GAAG,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;wBAE3D,UAAU,CAAC,QAAQ,CAAC,GAAG;4BACnB,GAAG,IAAI;4BACP,GAAG,CAAC,aAAa,IAAI,EAAE,aAAa,EAAE,CAAC;yBAC1C,CAAC;oBACN,CAAC;gBACL,CAAC;gBAED,KAAK,CAAC,QAAQ,CAAC,GAAG;oBACd,IAAI,EAAE,QAAQ;oBACd,UAAU;oBACV,UAAU,EAAE,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;iBAC3D,CAAC;YACN,CAAC;QACL,CAAC;QAED,KAAK,EAAE,CAAC;QACR,sCAAsC;QACtC,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;IAC3D,CAAC;IAES,gBAAgB,CAAC,OAAe,EAAE,iBAAyB;QACjE,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACzC,IAAI,CAAC,YAAY,EAAE,CAAC;YAChB,OAAO,KAAK,CAAC;QACjB,CAAC;QAED,KAAK,MAAM,SAAS,IAAI,YAAY,CAAC,UAAU,EAAE,CAAC;YAC9C,IAAI,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,iBAAiB,CAAC,EAAE,CAAC;gBAC/C,OAAO,IAAI,CAAC;YAChB,CAAC;QACL,CAAC;QACD,OAAO,KAAK,CAAC;IACjB,CAAC;CAEJ;AAED,SAAS,mBAAmB,CAAC,QAAkB;IAC3C,MAAM,UAAU,GAAG,IAAI,QAAQ,EAA4B,CAAC;IAC5D,KAAK,MAAM,aAAa,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC;QAC9C,KAAK,MAAM,QAAQ,IAAI,aAAa,CAAC,UAAU,EAAE,CAAC;YAC9C,KAAK,MAAM,aAAa,IAAI,kBAAkB,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC5D,UAAU,CAAC,GAAG,CAAC,aAAa,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC,CAAC,CAAC;YACvE,CAAC;QACL,CAAC;QACD,KAAK,MAAM,SAAS,IAAI,aAAa,CAAC,mBAAmB,EAAE,CAAC;YACxD,MAAM,mBAAmB,GAAG,UAAU,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;YAC3D,UAAU,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,EAAE,mBAAmB,CAAC,CAAC;QAC/D,CAAC;IACL,CAAC;IACD,MAAM,GAAG,GAAG,IAAI,GAAG,EAAkB,CAAC;IACtC,KAAK,MAAM,CAAC,IAAI,EAAE,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,IAAI,UAAU,EAAE,CAAC;QAClD,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,IAAI,QAAQ,EAAE,EAAE,MAAM,CAAC,CAAC;IAC3C,CAAC;IACD,OAAO,GAAG,CAAC;AACf,CAAC;AAED,SAAS,iBAAiB,CAAC,QAAkB;IACzC,MAAM,GAAG,GAAG,IAAI,GAAG,EAAwB,CAAC;IAC5C,KAAK,MAAM,aAAa,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC;QAC9C,MAAM,KAAK,GAAG,aAAa,CAAC,eAAe,CAAC;QAC5C,GAAG,CAAC,GAAG,CAAC,aAAa,CAAC,IAAI,EAAE;YACxB,IAAI,EAAE,aAAa,CAAC,IAAI;YACxB,UAAU,EAAE,qBAAqB,CAAC,KAAK,CAAC;YACxC,UAAU,EAAE,EAAE,CAAE,+CAA+C;SAClE,CAAC,CAAC;IACP,CAAC;IACD,OAAO,GAAG,CAAC;AACf,CAAC;AAED,SAAS,qBAAqB,CAAC,KAAiB;IAC5C,MAAM,UAAU,GAAyC,EAAE,CAAC;IAC5D,MAAM,GAAG,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IAC/D,KAAK,MAAM,QAAQ,IAAI,GAAG,EAAE,CAAC;QACzB,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG;YACxB,IAAI,EAAE,QAAQ,CAAC,IAAI;YACnB,YAAY,EAAE,QAAQ,CAAC,YAAY;SACtC,CAAC;IACN,CAAC;IACD,OAAO,UAAU,CAAC;AACtB,CAAC"}
+7
View File
@@ -0,0 +1,7 @@
/******************************************************************************
* This file was generated by langium-cli 4.2.1.
* DO NOT EDIT MANUALLY!
******************************************************************************/
import type { Grammar } from '../../languages/generated/ast.js';
export declare const LangiumGrammarGrammar: () => Grammar;
//# sourceMappingURL=grammar.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"grammar.d.ts","sourceRoot":"","sources":["../../../src/grammar/generated/grammar.ts"],"names":[],"mappings":"AAAA;;;gFAGgF;AAEhF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,kCAAkC,CAAC;AAIhE,eAAO,MAAM,qBAAqB,QAAO,OAAgz5D,CAAC"}
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"grammar.js","sourceRoot":"","sources":["../../../src/grammar/generated/grammar.ts"],"names":[],"mappings":"AAAA;;;gFAGgF;AAGhF,OAAO,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AAEpE,IAAI,2BAAgD,CAAC;AACrD,MAAM,CAAC,MAAM,qBAAqB,GAAG,GAAY,EAAE,CAAC,2BAA2B,IAAI,CAAC,2BAA2B,GAAG,mBAAmB,CAAC,it5DAAit5D,CAAC,CAAC,CAAC"}
+17
View File
@@ -0,0 +1,17 @@
/******************************************************************************
* This file was generated by langium-cli 4.2.1.
* DO NOT EDIT MANUALLY!
******************************************************************************/
import type { Module } from '../../dependency-injection.js';
import type { LangiumSharedCoreServices, LangiumCoreServices, LangiumGeneratedCoreServices, LangiumGeneratedSharedCoreServices } from '../../services.js';
import type { IParserConfig } from '../../parser/parser-config.js';
export declare const LangiumGrammarLanguageMetaData: {
readonly languageId: "langium";
readonly fileExtensions: readonly [".langium"];
readonly caseInsensitive: false;
readonly mode: "production";
};
export declare const LangiumGrammarParserConfig: IParserConfig;
export declare const LangiumGrammarGeneratedSharedModule: Module<LangiumSharedCoreServices, LangiumGeneratedSharedCoreServices>;
export declare const LangiumGrammarGeneratedModule: Module<LangiumCoreServices, LangiumGeneratedCoreServices>;
//# sourceMappingURL=module.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../../../src/grammar/generated/module.ts"],"names":[],"mappings":"AAAA;;;gFAGgF;AAIhF,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,+BAA+B,CAAC;AAC5D,OAAO,KAAK,EAAE,yBAAyB,EAAE,mBAAmB,EAAE,4BAA4B,EAAE,kCAAkC,EAAE,MAAM,mBAAmB,CAAC;AAC1J,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAGnE,eAAO,MAAM,8BAA8B;;;;;CAKN,CAAC;AAEtC,eAAO,MAAM,0BAA0B,EAAE,aAExC,CAAC;AAEF,eAAO,MAAM,mCAAmC,EAAE,MAAM,CAAC,yBAAyB,EAAE,kCAAkC,CAErH,CAAC;AAEF,eAAO,MAAM,6BAA6B,EAAE,MAAM,CAAC,mBAAmB,EAAE,4BAA4B,CAMnG,CAAC"}
+26
View File
@@ -0,0 +1,26 @@
/******************************************************************************
* This file was generated by langium-cli 4.2.1.
* DO NOT EDIT MANUALLY!
******************************************************************************/
import { LangiumGrammarAstReflection } from '../../languages/generated/ast.js';
import { LangiumGrammarGrammar } from './grammar.js';
export const LangiumGrammarLanguageMetaData = {
languageId: 'langium',
fileExtensions: ['.langium'],
caseInsensitive: false,
mode: 'production'
};
export const LangiumGrammarParserConfig = {
maxLookahead: 3,
};
export const LangiumGrammarGeneratedSharedModule = {
AstReflection: () => new LangiumGrammarAstReflection()
};
export const LangiumGrammarGeneratedModule = {
Grammar: () => LangiumGrammarGrammar(),
LanguageMetaData: () => LangiumGrammarLanguageMetaData,
parser: {
ParserConfig: () => LangiumGrammarParserConfig
}
};
//# sourceMappingURL=module.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"module.js","sourceRoot":"","sources":["../../../src/grammar/generated/module.ts"],"names":[],"mappings":"AAAA;;;gFAGgF;AAGhF,OAAO,EAAE,2BAA2B,EAAE,MAAM,kCAAkC,CAAC;AAI/E,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAErD,MAAM,CAAC,MAAM,8BAA8B,GAAG;IAC1C,UAAU,EAAE,SAAS;IACrB,cAAc,EAAE,CAAC,UAAU,CAAC;IAC5B,eAAe,EAAE,KAAK;IACtB,IAAI,EAAE,YAAY;CACe,CAAC;AAEtC,MAAM,CAAC,MAAM,0BAA0B,GAAkB;IACrD,YAAY,EAAE,CAAC;CAClB,CAAC;AAEF,MAAM,CAAC,MAAM,mCAAmC,GAA0E;IACtH,aAAa,EAAE,GAAG,EAAE,CAAC,IAAI,2BAA2B,EAAE;CACzD,CAAC;AAEF,MAAM,CAAC,MAAM,6BAA6B,GAA8D;IACpG,OAAO,EAAE,GAAG,EAAE,CAAC,qBAAqB,EAAE;IACtC,gBAAgB,EAAE,GAAG,EAAE,CAAC,8BAA8B;IACtD,MAAM,EAAE;QACJ,YAAY,EAAE,GAAG,EAAE,CAAC,0BAA0B;KACjD;CACJ,CAAC"}
+27
View File
@@ -0,0 +1,27 @@
/******************************************************************************
* 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.
*
* @module langium/grammar
*/
export * from './generated/grammar.js';
export * from './generated/module.js';
export * from './lsp/grammar-call-hierarchy.js';
export * from './lsp/grammar-code-actions.js';
export * from './lsp/grammar-completion-provider.js';
export * from './lsp/grammar-definition.js';
export * from './lsp/grammar-folding-ranges.js';
export * from './lsp/grammar-formatter.js';
export * from './lsp/grammar-semantic-tokens.js';
export * from './references/grammar-naming.js';
export * from './references/grammar-references.js';
export * from './references/grammar-scope.js';
export * from './validation/types-validator.js';
export * from './validation/validation-resources-collector.js';
export * from './validation/validator.js';
export * from './type-system/index.js';
export * from './langium-grammar-module.js';
export * from './internal-grammar-util.js';
export * from './ast-reflection-interpreter.js';
//# sourceMappingURL=index.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/grammar/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,cAAc,wBAAwB,CAAC;AACvC,cAAc,uBAAuB,CAAC;AACtC,cAAc,iCAAiC,CAAC;AAChD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,sCAAsC,CAAC;AACrD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,iCAAiC,CAAC;AAChD,cAAc,4BAA4B,CAAC;AAC3C,cAAc,kCAAkC,CAAC;AACjD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,oCAAoC,CAAC;AACnD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,iCAAiC,CAAC;AAChD,cAAc,gDAAgD,CAAC;AAC/D,cAAc,2BAA2B,CAAC;AAC1C,cAAc,wBAAwB,CAAC;AACvC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,iCAAiC,CAAC"}
+30
View File
@@ -0,0 +1,30 @@
/******************************************************************************
* 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.
*
* @module langium/grammar
*/
// This file contains Langium grammar language internals.
// It is not supposed to be exported with the general `langium` export.
// Instead, it is available from `langium/grammar`.
export * from './generated/grammar.js';
export * from './generated/module.js';
export * from './lsp/grammar-call-hierarchy.js';
export * from './lsp/grammar-code-actions.js';
export * from './lsp/grammar-completion-provider.js';
export * from './lsp/grammar-definition.js';
export * from './lsp/grammar-folding-ranges.js';
export * from './lsp/grammar-formatter.js';
export * from './lsp/grammar-semantic-tokens.js';
export * from './references/grammar-naming.js';
export * from './references/grammar-references.js';
export * from './references/grammar-scope.js';
export * from './validation/types-validator.js';
export * from './validation/validation-resources-collector.js';
export * from './validation/validator.js';
export * from './type-system/index.js';
export * from './langium-grammar-module.js';
export * from './internal-grammar-util.js';
export * from './ast-reflection-interpreter.js';
//# sourceMappingURL=index.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/grammar/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,yDAAyD;AACzD,uEAAuE;AACvE,mDAAmD;AAEnD,cAAc,wBAAwB,CAAC;AACvC,cAAc,uBAAuB,CAAC;AACtC,cAAc,iCAAiC,CAAC;AAChD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,sCAAsC,CAAC;AACrD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,iCAAiC,CAAC;AAChD,cAAc,4BAA4B,CAAC;AAC3C,cAAc,kCAAkC,CAAC;AACjD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,oCAAoC,CAAC;AACnD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,iCAAiC,CAAC;AAChD,cAAc,gDAAgD,CAAC;AAC/D,cAAc,2BAA2B,CAAC;AAC1C,cAAc,wBAAwB,CAAC;AACvC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,iCAAiC,CAAC"}
+35
View File
@@ -0,0 +1,35 @@
/******************************************************************************
* 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 { URI } from '../utils/uri-utils.js';
import type { LangiumDocuments } from '../workspace/documents.js';
import * as ast from '../languages/generated/ast.js';
import type { LangiumGrammarServices } from './langium-grammar-module.js';
import type { IParserConfig } from '../parser/parser-config.js';
import type { LanguageMetaData } from '../languages/language-meta-data.js';
import type { Module } from '../dependency-injection.js';
import type { LangiumServices, LangiumSharedServices } from '../lsp/lsp-services.js';
export declare function hasDataTypeReturn(rule: ast.ParserRule): boolean;
export declare function isStringGrammarType(type: ast.AbstractType | ast.TypeDefinition): boolean;
export declare function getTypeNameWithoutError(type?: ast.AbstractType | ast.Action): string | undefined;
export declare function resolveImportUri(imp: ast.GrammarImport): URI | undefined;
export declare function resolveImport(documents: LangiumDocuments, imp: ast.GrammarImport): ast.Grammar | undefined;
export declare function resolveTransitiveImports(documents: LangiumDocuments, grammar: ast.Grammar): ast.Grammar[];
export declare function resolveTransitiveImports(documents: LangiumDocuments, importNode: ast.GrammarImport): ast.Grammar[];
export declare function extractAssignments(element: ast.AbstractElement): ast.Assignment[];
export declare function isPrimitiveGrammarType(type: string): boolean;
/**
* Create an instance of the language services for the given grammar. This function is very
* useful when the grammar is defined on-the-fly, for example in tests of the Langium framework.
*/
export declare function createServicesForGrammar<L extends LangiumServices = LangiumServices, S extends LangiumSharedServices = LangiumSharedServices>(config: {
grammar: string | ast.Grammar;
grammarServices?: LangiumGrammarServices;
parserConfig?: IParserConfig;
languageMetaData?: LanguageMetaData;
module?: Module<L, unknown>;
sharedModule?: Module<S, unknown>;
}): Promise<L>;
//# sourceMappingURL=internal-grammar-util.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"internal-grammar-util.d.ts","sourceRoot":"","sources":["../../src/grammar/internal-grammar-util.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAEhF,OAAO,EAAE,GAAG,EAAE,MAAM,uBAAuB,CAAC;AAC5C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAElE,OAAO,KAAK,GAAG,MAAM,+BAA+B,CAAC;AAGrD,OAAO,KAAK,EAAE,sBAAsB,EAAC,MAAM,6BAA6B,CAAC;AAEzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAC;AAC3E,OAAO,KAAK,EAAE,MAAM,EAAC,MAAM,4BAA4B,CAAC;AAGxD,OAAO,KAAK,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAMrF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,GAAG,CAAC,UAAU,GAAG,OAAO,CAG/D;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,GAAG,CAAC,YAAY,GAAG,GAAG,CAAC,cAAc,GAAG,OAAO,CAExF;AAmCD,wBAAgB,uBAAuB,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,YAAY,GAAG,GAAG,CAAC,MAAM,GAAG,MAAM,GAAG,SAAS,CAShG;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,GAAG,CAAC,aAAa,GAAG,GAAG,GAAG,SAAS,CAUxE;AAED,wBAAgB,aAAa,CAAC,SAAS,EAAE,gBAAgB,EAAE,GAAG,EAAE,GAAG,CAAC,aAAa,GAAG,GAAG,CAAC,OAAO,GAAG,SAAS,CAc1G;AAED,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,gBAAgB,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,GAAG,GAAG,CAAC,OAAO,EAAE,CAAA;AAC1G,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,gBAAgB,EAAE,UAAU,EAAE,GAAG,CAAC,aAAa,GAAG,GAAG,CAAC,OAAO,EAAE,CAAA;AA2CnH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,GAAG,CAAC,eAAe,GAAG,GAAG,CAAC,UAAU,EAAE,CAYjF;AAID,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE5D;AAED;;;GAGG;AACH,wBAAsB,wBAAwB,CAAC,CAAC,SAAS,eAAe,GAAG,eAAe,EAAE,CAAC,SAAS,qBAAqB,GAAG,qBAAqB,EAAE,MAAM,EAAE;IACzJ,OAAO,EAAE,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC;IAC9B,eAAe,CAAC,EAAE,sBAAsB,CAAC;IACzC,YAAY,CAAC,EAAE,aAAa,CAAC;IAC7B,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,EAAE,OAAO,CAAC,CAAA;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC,CAAC,EAAE,OAAO,CAAC,CAAA;CACpC,GAAG,OAAO,CAAC,CAAC,CAAC,CAkCb"}
+198
View File
@@ -0,0 +1,198 @@
/******************************************************************************
* 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 { URI } from '../utils/uri-utils.js';
import * as ast from '../languages/generated/ast.js';
import { getDocument } from '../utils/ast-utils.js';
import { UriUtils } from '../utils/uri-utils.js';
import { createLangiumGrammarServices } from './langium-grammar-module.js';
import { inject } from '../dependency-injection.js';
import { createDefaultModule, createDefaultSharedModule } from '../lsp/default-lsp-module.js';
import { EmptyFileSystem } from '../workspace/file-system-provider.js';
import { interpretAstReflection } from './ast-reflection-interpreter.js';
import { getTypeName, isDataType } from '../utils/grammar-utils.js';
export function hasDataTypeReturn(rule) {
const returnType = rule.returnType?.ref;
return rule.dataType !== undefined || (ast.isType(returnType) && isDataType(returnType));
}
export function isStringGrammarType(type) {
return isStringTypeInternal(type, new Set());
}
function isStringTypeInternal(type, visited) {
if (visited.has(type)) {
return true;
}
else {
visited.add(type);
}
if (ast.isParserRule(type)) {
if (type.dataType) {
return type.dataType === 'string';
}
if (type.returnType?.ref) {
return isStringTypeInternal(type.returnType.ref, visited);
}
}
else if (ast.isType(type)) {
return isStringTypeInternal(type.type, visited);
}
else if (ast.isArrayType(type)) {
return false;
}
else if (ast.isReferenceType(type)) {
return false;
}
else if (ast.isUnionType(type)) {
return type.types.every(e => isStringTypeInternal(e, visited));
}
else if (ast.isSimpleType(type)) {
if (type.primitiveType === 'string') {
return true;
}
else if (type.stringType) {
return true;
}
else if (type.typeRef?.ref) {
return isStringTypeInternal(type.typeRef.ref, visited);
}
}
return false;
}
export function getTypeNameWithoutError(type) {
if (!type) {
return undefined;
}
try {
return getTypeName(type);
}
catch {
return undefined;
}
}
export function resolveImportUri(imp) {
if (imp.path === undefined || imp.path.length === 0) {
return undefined;
}
const dirUri = UriUtils.dirname(getDocument(imp).uri);
let grammarPath = imp.path;
if (!grammarPath.endsWith('.langium')) {
grammarPath += '.langium';
}
return UriUtils.resolvePath(dirUri, grammarPath);
}
export function resolveImport(documents, imp) {
const resolvedUri = resolveImportUri(imp);
if (!resolvedUri) {
return undefined;
}
const resolvedDocument = documents.getDocument(resolvedUri);
if (!resolvedDocument) {
return undefined;
}
const node = resolvedDocument.parseResult.value;
if (ast.isGrammar(node)) {
return node;
}
return undefined;
}
export function resolveTransitiveImports(documents, grammarOrImport) {
if (ast.isGrammarImport(grammarOrImport)) {
const resolvedGrammar = resolveImport(documents, grammarOrImport);
if (resolvedGrammar) {
const transitiveGrammars = resolveTransitiveImportsInternal(documents, resolvedGrammar);
transitiveGrammars.push(resolvedGrammar);
return transitiveGrammars;
}
return [];
}
else {
return resolveTransitiveImportsInternal(documents, grammarOrImport);
}
}
/**
* Resolves all transitively imported grammars of the given grammar.
* In case of grammars importing each other in circular way, each grammar is remembered only once.
* The initial grammar will never be part of the result.
* @param documents the service to get all available Langium documents
* @param grammar the grammar to transitively resolve its imported grammars
* @param initialGrammar Even if the initial grammar transitively imports itself in circular way again, the initial grammar will not be part of the result!
* @param visited since grammars might import each other in circular way, this set remembers the already visited gramar URIs to prevent loops
* @param grammars the result set of already imported and resolved grammars
* @returns the collected `grammars` in a new array
*/
function resolveTransitiveImportsInternal(documents, grammar, initialGrammar = grammar, visited = new Set(), grammars = new Set()) {
const doc = getDocument(grammar);
if (initialGrammar !== grammar) {
grammars.add(grammar);
}
if (!visited.has(doc.uri)) {
visited.add(doc.uri);
for (const imp of grammar.imports) {
const importedGrammar = resolveImport(documents, imp);
if (importedGrammar) {
resolveTransitiveImportsInternal(documents, importedGrammar, initialGrammar, visited, grammars);
}
}
}
return Array.from(grammars);
}
export function extractAssignments(element) {
if (ast.isAssignment(element)) {
return [element];
}
else if (ast.isAlternatives(element) || ast.isGroup(element) || ast.isUnorderedGroup(element)) {
return element.elements.flatMap(e => extractAssignments(e));
}
else if (ast.isRuleCall(element) && element.rule.ref) {
if (ast.isInfixRule(element.rule.ref)) {
return [];
}
return extractAssignments(element.rule.ref.definition);
}
return [];
}
const primitiveTypes = ['string', 'number', 'boolean', 'Date', 'bigint'];
export function isPrimitiveGrammarType(type) {
return primitiveTypes.includes(type);
}
/**
* Create an instance of the language services for the given grammar. This function is very
* useful when the grammar is defined on-the-fly, for example in tests of the Langium framework.
*/
export async function createServicesForGrammar(config) {
const grammarServices = config.grammarServices ?? createLangiumGrammarServices(EmptyFileSystem).grammar;
const uri = URI.parse('memory:/grammar.langium');
const factory = grammarServices.shared.workspace.LangiumDocumentFactory;
const grammarDocument = typeof config.grammar === 'string'
? factory.fromString(config.grammar, uri)
: getDocument(config.grammar);
const grammarNode = grammarDocument.parseResult.value;
const documentBuilder = grammarServices.shared.workspace.DocumentBuilder;
await documentBuilder.build([grammarDocument], { validation: false });
const parserConfig = config.parserConfig ?? {
skipValidations: false
};
const languageMetaData = config.languageMetaData ?? {
caseInsensitive: false,
fileExtensions: ['.txt'],
languageId: grammarNode.name ?? 'UNKNOWN',
mode: 'development'
};
const generatedSharedModule = {
AstReflection: () => interpretAstReflection(grammarNode),
};
const generatedModule = {
Grammar: () => grammarNode,
LanguageMetaData: () => languageMetaData,
parser: {
ParserConfig: () => parserConfig
}
};
const shared = inject(createDefaultSharedModule(EmptyFileSystem), generatedSharedModule, config.sharedModule);
const services = inject(createDefaultModule({ shared }), generatedModule, config.module);
shared.ServiceRegistry.register(services);
return services;
}
//# sourceMappingURL=internal-grammar-util.js.map
File diff suppressed because one or more lines are too long
+34
View File
@@ -0,0 +1,34 @@
/******************************************************************************
* 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 { Module } from '../dependency-injection.js';
import type { DeepPartial } from '../services.js';
import type { LangiumServices, LangiumSharedServices, PartialLangiumServices, PartialLangiumSharedServices } from '../lsp/lsp-services.js';
import { type DefaultSharedModuleContext } from '../lsp/default-lsp-module.js';
import { LangiumGrammarValidator } from './validation/validator.js';
import { LangiumGrammarValidationResourcesCollector } from './validation/validation-resources-collector.js';
import { LangiumGrammarTypesValidator } from './validation/types-validator.js';
export type LangiumGrammarAddedServices = {
validation: {
LangiumGrammarValidator: LangiumGrammarValidator;
ValidationResourcesCollector: LangiumGrammarValidationResourcesCollector;
LangiumGrammarTypesValidator: LangiumGrammarTypesValidator;
};
};
export type LangiumGrammarServices = LangiumServices & LangiumGrammarAddedServices;
export declare const LangiumGrammarModule: Module<LangiumGrammarServices, PartialLangiumServices & LangiumGrammarAddedServices>;
/**
* Creates Langium grammar services, enriched with LSP functionality
*
* @param context Shared module context, used to create additional shared modules
* @param sharedModule Existing shared module to inject together with new shared services
* @param module Additional/modified service implementations for the language services
* @returns Shared services enriched with LSP services + Grammar services, per usual
*/
export declare function createLangiumGrammarServices(context: DefaultSharedModuleContext, sharedModule?: Module<LangiumSharedServices, PartialLangiumSharedServices>, module?: Module<LangiumGrammarServices, DeepPartial<LangiumServices & LangiumGrammarAddedServices>>): {
shared: LangiumSharedServices;
grammar: LangiumGrammarServices;
};
//# sourceMappingURL=langium-grammar-module.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"langium-grammar-module.d.ts","sourceRoot":"","sources":["../../src/grammar/langium-grammar-module.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAEhF,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,4BAA4B,CAAC;AACzD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,KAAK,EAAE,eAAe,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,4BAA4B,EAAE,MAAM,wBAAwB,CAAC;AAI3I,OAAO,EAAE,KAAK,0BAA0B,EAAkD,MAAM,8BAA8B,CAAC;AAI/H,OAAO,EAAE,uBAAuB,EAA4B,MAAM,2BAA2B,CAAC;AAU9F,OAAO,EAAE,0CAA0C,EAAE,MAAM,gDAAgD,CAAC;AAC5G,OAAO,EAAE,4BAA4B,EAAgC,MAAM,iCAAiC,CAAC;AAG7G,MAAM,MAAM,2BAA2B,GAAG;IACtC,UAAU,EAAE;QACR,uBAAuB,EAAE,uBAAuB,CAAC;QACjD,4BAA4B,EAAE,0CAA0C,CAAC;QACzE,4BAA4B,EAAE,4BAA4B,CAAC;KAC9D,CAAA;CACJ,CAAA;AAED,MAAM,MAAM,sBAAsB,GAAG,eAAe,GAAG,2BAA2B,CAAC;AAEnF,eAAO,MAAM,oBAAoB,EAAE,MAAM,CAAC,sBAAsB,EAAE,sBAAsB,GAAG,2BAA2B,CAsBrH,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,0BAA0B,EAC5E,YAAY,CAAC,EAAE,MAAM,CAAC,qBAAqB,EAAE,4BAA4B,CAAC,EAC1E,MAAM,CAAC,EAAE,MAAM,CAAC,sBAAsB,EAAE,WAAW,CAAC,eAAe,GAAG,2BAA2B,CAAC,CAAC,GAAG;IACtG,MAAM,EAAE,qBAAqB,CAAC;IAC9B,OAAO,EAAE,sBAAsB,CAAA;CAClC,CAyBA"}
+77
View File
@@ -0,0 +1,77 @@
/******************************************************************************
* 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 { LangiumGrammarTypeHierarchyProvider } from './lsp/grammar-type-hierarchy.js';
import { createDefaultModule, createDefaultSharedModule } from '../lsp/default-lsp-module.js';
import { inject } from '../dependency-injection.js';
import { LangiumGrammarGeneratedModule, LangiumGrammarGeneratedSharedModule } from './generated/module.js';
import { LangiumGrammarScopeComputation, LangiumGrammarScopeProvider } from './references/grammar-scope.js';
import { LangiumGrammarValidator, registerValidationChecks } from './validation/validator.js';
import { LangiumGrammarCodeActionProvider } from './lsp/grammar-code-actions.js';
import { LangiumGrammarCompletionProvider } from './lsp/grammar-completion-provider.js';
import { LangiumGrammarFoldingRangeProvider } from './lsp/grammar-folding-ranges.js';
import { LangiumGrammarFormatter } from './lsp/grammar-formatter.js';
import { LangiumGrammarSemanticTokenProvider } from './lsp/grammar-semantic-tokens.js';
import { LangiumGrammarNameProvider } from './references/grammar-naming.js';
import { LangiumGrammarReferences } from './references/grammar-references.js';
import { LangiumGrammarDefinitionProvider } from './lsp/grammar-definition.js';
import { LangiumGrammarCallHierarchyProvider } from './lsp/grammar-call-hierarchy.js';
import { LangiumGrammarValidationResourcesCollector } from './validation/validation-resources-collector.js';
import { LangiumGrammarTypesValidator, registerTypeValidationChecks } from './validation/types-validator.js';
import { DocumentState } from '../workspace/documents.js';
export const LangiumGrammarModule = {
validation: {
LangiumGrammarValidator: (services) => new LangiumGrammarValidator(services),
ValidationResourcesCollector: (services) => new LangiumGrammarValidationResourcesCollector(services),
LangiumGrammarTypesValidator: () => new LangiumGrammarTypesValidator(),
},
lsp: {
FoldingRangeProvider: (services) => new LangiumGrammarFoldingRangeProvider(services),
CodeActionProvider: (services) => new LangiumGrammarCodeActionProvider(services),
SemanticTokenProvider: (services) => new LangiumGrammarSemanticTokenProvider(services),
Formatter: () => new LangiumGrammarFormatter(),
DefinitionProvider: (services) => new LangiumGrammarDefinitionProvider(services),
CallHierarchyProvider: (services) => new LangiumGrammarCallHierarchyProvider(services),
TypeHierarchyProvider: (services) => new LangiumGrammarTypeHierarchyProvider(services),
CompletionProvider: (services) => new LangiumGrammarCompletionProvider(services)
},
references: {
ScopeComputation: (services) => new LangiumGrammarScopeComputation(services),
ScopeProvider: (services) => new LangiumGrammarScopeProvider(services),
References: (services) => new LangiumGrammarReferences(services),
NameProvider: () => new LangiumGrammarNameProvider()
}
};
/**
* Creates Langium grammar services, enriched with LSP functionality
*
* @param context Shared module context, used to create additional shared modules
* @param sharedModule Existing shared module to inject together with new shared services
* @param module Additional/modified service implementations for the language services
* @returns Shared services enriched with LSP services + Grammar services, per usual
*/
export function createLangiumGrammarServices(context, sharedModule, module) {
const shared = inject(createDefaultSharedModule(context), LangiumGrammarGeneratedSharedModule, sharedModule);
const grammar = inject(createDefaultModule({ shared }), LangiumGrammarGeneratedModule, LangiumGrammarModule, module);
addTypeCollectionPhase(shared, grammar);
shared.ServiceRegistry.register(grammar);
registerValidationChecks(grammar);
registerTypeValidationChecks(grammar);
if (!context.connection) {
// We don't run inside a language server
// Therefore, initialize the configuration provider instantly
shared.workspace.ConfigurationProvider.initialized({});
}
return { shared, grammar };
}
function addTypeCollectionPhase(sharedServices, grammarServices) {
const documentBuilder = sharedServices.workspace.DocumentBuilder;
documentBuilder.onDocumentPhase(DocumentState.IndexedReferences, async (document) => {
const typeCollector = grammarServices.validation.ValidationResourcesCollector;
const grammar = document.parseResult.value;
document.validationResources = typeCollector.collectValidationResources(grammar);
});
}
//# sourceMappingURL=langium-grammar-module.js.map
@@ -0,0 +1 @@
{"version":3,"file":"langium-grammar-module.js","sourceRoot":"","sources":["../../src/grammar/langium-grammar-module.ts"],"names":[],"mappings":"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"}
@@ -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.
******************************************************************************/
import type { CallHierarchyIncomingCall, CallHierarchyOutgoingCall } from 'vscode-languageserver';
import type { AstNode } from '../../syntax-tree.js';
import type { Stream } from '../../utils/stream.js';
import type { ReferenceDescription } from '../../workspace/ast-descriptions.js';
import { AbstractCallHierarchyProvider } from '../../lsp/call-hierarchy-provider.js';
export declare class LangiumGrammarCallHierarchyProvider extends AbstractCallHierarchyProvider {
protected getIncomingCalls(node: AstNode, references: Stream<ReferenceDescription>): CallHierarchyIncomingCall[] | undefined;
protected getOutgoingCalls(node: AstNode): CallHierarchyOutgoingCall[] | undefined;
}
//# sourceMappingURL=grammar-call-hierarchy.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"grammar-call-hierarchy.d.ts","sourceRoot":"","sources":["../../../src/grammar/lsp/grammar-call-hierarchy.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAEhF,OAAO,KAAK,EAAE,yBAAyB,EAAE,yBAAyB,EAAS,MAAM,uBAAuB,CAAC;AACzG,OAAO,KAAK,EAAE,OAAO,EAAW,MAAM,sBAAsB,CAAC;AAC7D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qCAAqC,CAAC;AAEhF,OAAO,EAAE,6BAA6B,EAAE,MAAM,sCAAsC,CAAC;AAKrF,qBAAa,mCAAoC,SAAQ,6BAA6B;IAElF,SAAS,CAAC,gBAAgB,CAAC,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,CAAC,oBAAoB,CAAC,GAAG,yBAAyB,EAAE,GAAG,SAAS;IAiD5H,SAAS,CAAC,gBAAgB,CAAC,IAAI,EAAE,OAAO,GAAG,yBAAyB,EAAE,GAAG,SAAS;CAmErF"}
+128
View File
@@ -0,0 +1,128 @@
/******************************************************************************
* 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 { SymbolKind } from 'vscode-languageserver';
import { AbstractCallHierarchyProvider } from '../../lsp/call-hierarchy-provider.js';
import { getContainerOfType, getDocument, streamAllContents } from '../../utils/ast-utils.js';
import { findLeafNodeAtOffset } from '../../utils/cst-utils.js';
import { isAbstractParserRule, isInfixRule, isParserRule, isRuleCall } from '../../languages/generated/ast.js';
export class LangiumGrammarCallHierarchyProvider extends AbstractCallHierarchyProvider {
getIncomingCalls(node, references) {
if (!isAbstractParserRule(node)) {
return undefined;
}
// This map is used to group incoming calls to avoid duplicates.
const uniqueRules = new Map();
references.forEach(ref => {
const doc = this.documents.getDocument(ref.sourceUri);
if (!doc) {
return;
}
const rootNode = doc.parseResult.value;
if (!rootNode.$cstNode) {
return;
}
const targetNode = findLeafNodeAtOffset(rootNode.$cstNode, ref.segment.offset);
if (!targetNode) {
return;
}
const parserRule = getContainerOfType(targetNode.astNode, isAbstractParserRule);
if (!parserRule || !parserRule.$cstNode) {
return;
}
const nameNode = this.nameProvider.getNameNode(parserRule);
if (!nameNode) {
return;
}
const refDocUri = ref.sourceUri.toString();
const ruleId = refDocUri + '@' + nameNode.text;
uniqueRules.has(ruleId) ?
uniqueRules.set(ruleId, { parserRule: parserRule.$cstNode, nameNode, targetNodes: [...uniqueRules.get(ruleId).targetNodes, targetNode], docUri: refDocUri })
: uniqueRules.set(ruleId, { parserRule: parserRule.$cstNode, nameNode, targetNodes: [targetNode], docUri: refDocUri });
});
if (uniqueRules.size === 0) {
return undefined;
}
return Array.from(uniqueRules.values()).map(rule => ({
from: {
kind: SymbolKind.Method,
name: rule.nameNode.text,
range: rule.parserRule.range,
selectionRange: rule.nameNode.range,
uri: rule.docUri
},
fromRanges: rule.targetNodes.map(node => node.range)
}));
}
getOutgoingCalls(node) {
if (isParserRule(node)) {
const ruleCalls = streamAllContents(node).filter(isRuleCall).toArray();
// This map is used to group outgoing calls to avoid duplicates.
const uniqueRules = new Map();
ruleCalls.forEach(ruleCall => {
const cstNode = ruleCall.$cstNode;
if (!cstNode) {
return;
}
const refCstNode = ruleCall.rule.ref?.$cstNode;
if (!refCstNode) {
return;
}
const refNameNode = this.nameProvider.getNameNode(refCstNode.astNode);
if (!refNameNode) {
return;
}
const refDocUri = getDocument(refCstNode.astNode).uri.toString();
const ruleId = refDocUri + '@' + refNameNode.text;
uniqueRules.has(ruleId) ?
uniqueRules.set(ruleId, { refCstNode: refCstNode, to: refNameNode, from: [...uniqueRules.get(ruleId).from, cstNode.range], docUri: refDocUri })
: uniqueRules.set(ruleId, { refCstNode: refCstNode, to: refNameNode, from: [cstNode.range], docUri: refDocUri });
});
if (uniqueRules.size === 0) {
return undefined;
}
return Array.from(uniqueRules.values()).map(rule => ({
to: {
kind: SymbolKind.Method,
name: rule.to.text,
range: rule.refCstNode.range,
selectionRange: rule.to.range,
uri: rule.docUri
},
fromRanges: rule.from
}));
}
else if (isInfixRule(node)) {
const ruleCall = node.call;
const cstNode = ruleCall.$cstNode;
if (!cstNode) {
return undefined;
}
const refCstNode = ruleCall.rule.ref?.$cstNode;
if (!refCstNode) {
return undefined;
}
const refNameNode = this.nameProvider.getNameNode(refCstNode.astNode);
if (!refNameNode) {
return undefined;
}
const refDocUri = getDocument(refCstNode.astNode).uri.toString();
return [{
to: {
kind: SymbolKind.Method,
name: refNameNode.text,
range: refCstNode.range,
selectionRange: refNameNode.range,
uri: refDocUri
},
fromRanges: [cstNode.range]
}];
}
else {
return undefined;
}
}
}
//# sourceMappingURL=grammar-call-hierarchy.js.map
@@ -0,0 +1 @@
{"version":3,"file":"grammar-call-hierarchy.js","sourceRoot":"","sources":["../../../src/grammar/lsp/grammar-call-hierarchy.ts"],"names":[],"mappings":"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"}
+42
View File
@@ -0,0 +1,42 @@
/******************************************************************************
* 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 { CodeActionParams } from 'vscode-languageserver-protocol';
import type { CodeAction, Command } from 'vscode-languageserver-types';
import * as ast from '../../languages/generated/ast.js';
import type { CodeActionProvider } from '../../lsp/code-action.js';
import type { LangiumServices } from '../../lsp/lsp-services.js';
import type { AstReflection } from '../../syntax-tree.js';
import type { MaybePromise } from '../../utils/promise-utils.js';
import type { LangiumDocument } from '../../workspace/documents.js';
import type { IndexManager } from '../../workspace/index-manager.js';
export declare class LangiumGrammarCodeActionProvider implements CodeActionProvider {
protected readonly reflection: AstReflection;
protected readonly indexManager: IndexManager;
constructor(services: LangiumServices);
getCodeActions(document: LangiumDocument<ast.Grammar>, params: CodeActionParams): MaybePromise<Array<Command | CodeAction>>;
private createCodeActions;
/**
* Adds missing returns for parser rule
*/
private fixMissingReturns;
private fixInvalidReturnsInfers;
private fixMissingInfer;
private fixMissingCrossRefTerminal;
private fixSuperfluousInfer;
private isRuleReplaceable;
private replaceRule;
private isDefinitionReplaceable;
private replaceDefinition;
private replaceParserRuleByTypeDeclaration;
private fixUnnecessaryFileExtension;
private makeUpperCase;
private addEntryKeyword;
private fixRegexTokens;
private fixCrossRefSyntax;
private addNewRule;
private lookInGlobalScope;
}
//# sourceMappingURL=grammar-code-actions.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"grammar-code-actions.d.ts","sourceRoot":"","sources":["../../../src/grammar/lsp/grammar-code-actions.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAIhF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AACvE,OAAO,KAAK,EAAE,UAAU,EAAE,OAAO,EAAY,MAAM,6BAA6B,CAAC;AACjF,OAAO,KAAK,GAAG,MAAM,kCAAkC,CAAC;AACxD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AACnE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,KAAK,EAAE,aAAa,EAA4B,MAAM,sBAAsB,CAAC;AAGpF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAOjE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAGrE,qBAAa,gCAAiC,YAAW,kBAAkB;IAEvE,SAAS,CAAC,QAAQ,CAAC,UAAU,EAAE,aAAa,CAAC;IAC7C,SAAS,CAAC,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;gBAElC,QAAQ,EAAE,eAAe;IAKrC,cAAc,CAAC,QAAQ,EAAE,eAAe,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,gBAAgB,GAAG,YAAY,CAAC,KAAK,CAAC,OAAO,GAAG,UAAU,CAAC,CAAC;IAS3H,OAAO,CAAC,iBAAiB;IAmDzB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAoBzB,OAAO,CAAC,uBAAuB;IAqB/B,OAAO,CAAC,eAAe;IAuBvB,OAAO,CAAC,0BAA0B;IAwBlC,OAAO,CAAC,mBAAmB;IAoB3B,OAAO,CAAC,iBAAiB;IAOzB,OAAO,CAAC,WAAW;IAInB,OAAO,CAAC,uBAAuB;IAS/B,OAAO,CAAC,iBAAiB;IAUzB,OAAO,CAAC,kCAAkC;IA8B1C,OAAO,CAAC,2BAA2B;IAwBnC,OAAO,CAAC,aAAa;IAwBrB,OAAO,CAAC,eAAe;IAiBvB,OAAO,CAAC,cAAc;IA4BtB,OAAO,CAAC,iBAAiB;IAiBzB,OAAO,CAAC,UAAU;IA6BlB,OAAO,CAAC,iBAAiB;CA0E5B"}
+458
View File
@@ -0,0 +1,458 @@
/******************************************************************************
* 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 { CodeActionKind } from 'vscode-languageserver';
import * as ast from '../../languages/generated/ast.js';
import { getContainerOfType } from '../../utils/ast-utils.js';
import { findLeafNodeAtOffset } from '../../utils/cst-utils.js';
import { escapeRegExp } from '../../utils/regexp-utils.js';
import { UriUtils } from '../../utils/uri-utils.js';
import { DocumentValidator } from '../../validation/document-validator.js';
import { IssueCodes } from '../validation/validator.js';
export class LangiumGrammarCodeActionProvider {
constructor(services) {
this.reflection = services.shared.AstReflection;
this.indexManager = services.shared.workspace.IndexManager;
}
getCodeActions(document, params) {
const result = [];
const acceptor = (ca) => ca && result.push(ca);
for (const diagnostic of params.context.diagnostics) {
this.createCodeActions(diagnostic, document, acceptor);
}
return result;
}
createCodeActions(diagnostic, document, accept) {
switch (diagnostic.data?.code) {
case IssueCodes.GrammarNameUppercase:
case IssueCodes.RuleNameUppercase:
accept(this.makeUpperCase(diagnostic, document));
break;
case IssueCodes.UseRegexTokens:
accept(this.fixRegexTokens(diagnostic, document));
break;
case IssueCodes.EntryRuleTokenSyntax:
accept(this.addEntryKeyword(diagnostic, document));
break;
case IssueCodes.CrossRefTokenSyntax:
accept(this.fixCrossRefSyntax(diagnostic, document));
break;
case IssueCodes.ParserRuleToTypeDecl:
accept(this.replaceParserRuleByTypeDeclaration(diagnostic, document));
break;
case IssueCodes.UnnecessaryFileExtension:
accept(this.fixUnnecessaryFileExtension(diagnostic, document));
break;
case IssueCodes.MissingReturns:
accept(this.fixMissingReturns(diagnostic, document));
break;
case IssueCodes.InvalidInfers:
case IssueCodes.InvalidReturns:
accept(this.fixInvalidReturnsInfers(diagnostic, document));
break;
case IssueCodes.MissingInfer:
accept(this.fixMissingInfer(diagnostic, document));
break;
case IssueCodes.MissingCrossRefTerminal:
accept(this.fixMissingCrossRefTerminal(diagnostic, document));
break;
case IssueCodes.SuperfluousInfer:
accept(this.fixSuperfluousInfer(diagnostic, document));
break;
case DocumentValidator.LinkingError: {
const data = diagnostic.data;
if (data && data.containerType === 'RuleCall' && data.property === 'rule') {
accept(this.addNewRule(diagnostic, data, document));
}
if (data) {
this.lookInGlobalScope(diagnostic, data, document).forEach(accept);
}
break;
}
}
return undefined;
}
/**
* Adds missing returns for parser rule
*/
fixMissingReturns(diagnostic, document) {
const text = document.textDocument.getText(diagnostic.range);
if (text) {
return {
title: `Add explicit return type for parser rule ${text}`,
kind: CodeActionKind.QuickFix,
diagnostics: [diagnostic],
edit: {
changes: {
[document.textDocument.uri]: [{
range: diagnostic.range,
newText: `${text} returns ${text}` // suggestion adds missing 'return'
}]
}
}
};
}
return undefined;
}
fixInvalidReturnsInfers(diagnostic, document) {
const data = diagnostic.data;
if (data && data.actionSegment) {
const text = document.textDocument.getText(data.actionSegment.range);
return {
title: `Correct ${text} usage`,
kind: CodeActionKind.QuickFix,
diagnostics: [diagnostic],
edit: {
changes: {
[document.textDocument.uri]: [{
range: data.actionSegment.range,
newText: text === 'infers' ? 'returns' : 'infers'
}]
}
}
};
}
return undefined;
}
fixMissingInfer(diagnostic, document) {
const data = diagnostic.data;
if (data && data.actionSegment) {
return {
title: "Correct 'infer' usage",
kind: CodeActionKind.QuickFix,
diagnostics: [diagnostic],
edit: {
changes: {
[document.textDocument.uri]: [{
range: {
start: data.actionSegment.range.end,
end: data.actionSegment.range.end
},
newText: 'infer '
}]
}
}
};
}
return undefined;
}
fixMissingCrossRefTerminal(diagnostic, document) {
const grammar = document.parseResult.value;
const idTerminal = grammar.rules.find(rule => ast.isTerminalRule(rule) && rule.name === 'ID');
if (idTerminal) {
return {
title: 'Use ID token to resolve cross-reference',
kind: CodeActionKind.QuickFix,
diagnostics: [diagnostic],
edit: {
changes: {
[document.textDocument.uri]: [{
range: {
start: diagnostic.range.end,
end: diagnostic.range.end
},
newText: ':ID'
}]
}
}
};
}
return undefined;
}
fixSuperfluousInfer(diagnostic, document) {
const data = diagnostic.data;
if (data && data.actionRange) {
return {
title: "Remove the 'infer' keyword",
kind: CodeActionKind.QuickFix,
diagnostics: [diagnostic],
edit: {
changes: {
[document.textDocument.uri]: [{
range: data.actionRange,
newText: ''
}]
}
}
};
}
return undefined;
}
isRuleReplaceable(rule) {
/** at the moment, only "pure" parser rules are supported:
* - supported are only Alternatives (recursively) and "infers"
* - "returns" is not relevant, since cross-references would not refer to the parser rule, but to its "return type" instead
*/
return !rule.fragment && !rule.entry && rule.parameters.length === 0 && !rule.returnType && !rule.dataType;
}
replaceRule(rule) {
const type = rule.inferredType ?? rule;
return type.name;
}
isDefinitionReplaceable(node) {
if (ast.isRuleCall(node)) {
return node.arguments.length === 0 && ast.isParserRule(node.rule.ref) && this.isRuleReplaceable(node.rule.ref);
}
if (ast.isAlternatives(node)) {
return node.elements.every(child => this.isDefinitionReplaceable(child));
}
return false;
}
replaceDefinition(node) {
if (ast.isRuleCall(node) && node.rule.ref) {
return node.rule.ref.name;
}
if (ast.isAlternatives(node)) {
return node.elements.map(child => this.replaceDefinition(child)).join(' | ');
}
throw new Error('missing code for ' + node);
}
replaceParserRuleByTypeDeclaration(diagnostic, document) {
const rootCst = document.parseResult.value.$cstNode;
if (rootCst) {
const offset = document.textDocument.offsetAt(diagnostic.range.start);
const cstNode = findLeafNodeAtOffset(rootCst, offset);
const rule = getContainerOfType(cstNode?.astNode, ast.isParserRule);
if (rule && rule.$cstNode) {
const isReplaceable = this.isRuleReplaceable(rule) && this.isDefinitionReplaceable(rule.definition);
if (isReplaceable) {
const newText = `type ${this.replaceRule(rule)} = ${this.replaceDefinition(rule.definition)};`;
return {
title: 'Replace with type declaration',
kind: CodeActionKind.QuickFix,
diagnostics: [diagnostic],
isPreferred: true,
edit: {
changes: {
[document.textDocument.uri]: [{
range: diagnostic.range,
newText
}]
}
}
};
}
}
}
return undefined;
}
fixUnnecessaryFileExtension(diagnostic, document) {
const end = { ...diagnostic.range.end };
end.character -= 1;
const start = { ...end };
start.character -= '.langium'.length;
return {
title: 'Remove file extension',
kind: CodeActionKind.QuickFix,
diagnostics: [diagnostic],
isPreferred: true,
edit: {
changes: {
[document.textDocument.uri]: [{
range: {
start,
end
},
newText: ''
}]
}
}
};
}
makeUpperCase(diagnostic, document) {
const range = {
start: diagnostic.range.start,
end: {
line: diagnostic.range.start.line,
character: diagnostic.range.start.character + 1
}
};
return {
title: 'First letter to upper case',
kind: CodeActionKind.QuickFix,
diagnostics: [diagnostic],
isPreferred: true,
edit: {
changes: {
[document.textDocument.uri]: [{
range,
newText: document.textDocument.getText(range).toUpperCase()
}]
}
}
};
}
addEntryKeyword(diagnostic, document) {
return {
title: 'Add entry keyword',
kind: CodeActionKind.QuickFix,
diagnostics: [diagnostic],
isPreferred: true,
edit: {
changes: {
[document.textDocument.uri]: [{
range: { start: diagnostic.range.start, end: diagnostic.range.start },
newText: 'entry '
}]
}
}
};
}
fixRegexTokens(diagnostic, document) {
const offset = document.textDocument.offsetAt(diagnostic.range.start);
const rootCst = document.parseResult.value.$cstNode;
if (rootCst) {
const cstNode = findLeafNodeAtOffset(rootCst, offset);
const container = getContainerOfType(cstNode?.astNode, ast.isCharacterRange);
if (container && container.right && container.$cstNode) {
const left = container.left.value;
const right = container.right.value;
return {
title: 'Refactor into regular expression',
kind: CodeActionKind.QuickFix,
diagnostics: [diagnostic],
isPreferred: true,
edit: {
changes: {
[document.textDocument.uri]: [{
range: container.$cstNode.range,
newText: `/[${escapeRegExp(left)}-${escapeRegExp(right)}]/`
}]
}
}
};
}
}
return undefined;
}
fixCrossRefSyntax(diagnostic, document) {
return {
title: "Replace '|' with ':'",
kind: CodeActionKind.QuickFix,
diagnostics: [diagnostic],
isPreferred: true,
edit: {
changes: {
[document.textDocument.uri]: [{
range: diagnostic.range,
newText: ':'
}]
}
}
};
}
addNewRule(diagnostic, data, document) {
const offset = document.textDocument.offsetAt(diagnostic.range.start);
const rootCst = document.parseResult.value.$cstNode;
if (rootCst) {
const cstNode = findLeafNodeAtOffset(rootCst, offset);
const container = getContainerOfType(cstNode?.astNode, ast.isParserRule);
if (container && container.$cstNode) {
return {
title: `Add new rule '${data.refText}'`,
kind: CodeActionKind.QuickFix,
diagnostics: [diagnostic],
isPreferred: false,
edit: {
changes: {
[document.textDocument.uri]: [{
range: {
start: container.$cstNode.range.end,
end: container.$cstNode.range.end
},
newText: '\n\n' + data.refText + ':\n /* TODO implement rule */ {infer ' + data.refText + '};'
}]
}
}
};
}
}
return undefined;
}
lookInGlobalScope(diagnostic, data, document) {
const refInfo = {
container: {
$type: data.containerType
},
property: data.property,
reference: {
$refText: data.refText
}
};
const referenceType = this.reflection.getReferenceType(refInfo);
const candidates = this.indexManager.allElements(referenceType).filter(e => e.name === data.refText);
const result = [];
let shortestPathIndex = -1;
let shortestPathLength = -1;
for (const candidate of candidates) {
if (UriUtils.equals(candidate.documentUri, document.uri)) {
continue;
}
// Find an import path and a position to insert the import
const importPath = getRelativeImport(document.uri, candidate.documentUri);
let position;
let suffix = '';
const grammar = document.parseResult.value;
const nextImport = grammar.imports.find(imp => imp.path && importPath < imp.path);
if (nextImport) {
// Insert the new import alphabetically
position = nextImport.$cstNode?.range.start;
}
else if (grammar.imports.length > 0) {
// Put the new import after the last import
const rangeEnd = grammar.imports[grammar.imports.length - 1].$cstNode.range.end;
if (rangeEnd) {
position = { line: rangeEnd.line + 1, character: 0 };
}
}
else if (grammar.rules.length > 0) {
// Put the new import before the first rule
position = grammar.rules[0].$cstNode?.range.start;
suffix = '\n';
}
if (position) {
if (shortestPathIndex < 0 || importPath.length < shortestPathLength) {
shortestPathIndex = result.length;
shortestPathLength = importPath.length;
}
// Add an import declaration for the candidate in the global scope
result.push({
title: `Add import to '${importPath}'`,
kind: CodeActionKind.QuickFix,
diagnostics: [diagnostic],
isPreferred: false,
edit: {
changes: {
[document.textDocument.uri]: [{
range: {
start: position,
end: position
},
newText: `import '${importPath}'\n${suffix}`
}]
}
}
});
}
}
// Mark the code action with the shortest import path as preferred
if (shortestPathIndex >= 0) {
result[shortestPathIndex].isPreferred = true;
}
return result;
}
}
function getRelativeImport(source, target) {
const sourceDir = UriUtils.dirname(source);
let relativePath = UriUtils.relative(sourceDir, target);
if (!relativePath.startsWith('./') && !relativePath.startsWith('../')) {
relativePath = './' + relativePath;
}
if (relativePath.endsWith('.langium')) {
relativePath = relativePath.substring(0, relativePath.length - '.langium'.length);
}
return relativePath;
}
//# sourceMappingURL=grammar-code-actions.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,18 @@
/******************************************************************************
* 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 { NextFeature } from '../../lsp/completion/follow-element-computation.js';
import { DefaultCompletionProvider, type CompletionAcceptor, type CompletionContext } from '../../lsp/completion/completion-provider.js';
import type { MaybePromise } from '../../utils/promise-utils.js';
import type { AbstractElement } from '../../languages/generated/ast.js';
import type { LangiumServices } from '../../lsp/lsp-services.js';
export declare class LangiumGrammarCompletionProvider extends DefaultCompletionProvider {
private readonly documents;
constructor(services: LangiumServices);
protected completionFor(context: CompletionContext, next: NextFeature<AbstractElement>, acceptor: CompletionAcceptor): MaybePromise<void>;
private completeImportPath;
private getAllFiles;
}
//# sourceMappingURL=grammar-completion-provider.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"grammar-completion-provider.d.ts","sourceRoot":"","sources":["../../../src/grammar/lsp/grammar-completion-provider.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAIhF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oDAAoD,CAAC;AACtF,OAAO,EAAE,yBAAyB,EAAE,KAAK,kBAAkB,EAAE,KAAK,iBAAiB,EAAE,MAAM,6CAA6C,CAAC;AACzI,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAGjE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AAGxE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAEjE,qBAAa,gCAAiC,SAAQ,yBAAyB;IAE3E,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAyB;gBAEvC,QAAQ,EAAE,eAAe;cAKlB,aAAa,CAAC,OAAO,EAAE,iBAAiB,EAAE,IAAI,EAAE,WAAW,CAAC,eAAe,CAAC,EAAE,QAAQ,EAAE,kBAAkB,GAAG,YAAY,CAAC,IAAI,CAAC;IASlJ,OAAO,CAAC,kBAAkB;IAmC1B,OAAO,CAAC,WAAW;CAmBtB"}
@@ -0,0 +1,78 @@
/******************************************************************************
* 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 { CompletionItemKind } from 'vscode-languageserver-types';
import { DefaultCompletionProvider } from '../../lsp/completion/completion-provider.js';
import { getContainerOfType } from '../../utils/ast-utils.js';
import { isAssignment } from '../../languages/generated/ast.js';
import { UriUtils } from '../../utils/uri-utils.js';
export class LangiumGrammarCompletionProvider extends DefaultCompletionProvider {
constructor(services) {
super(services);
this.documents = () => services.shared.workspace.LangiumDocuments;
}
completionFor(context, next, acceptor) {
const assignment = getContainerOfType(next.feature, isAssignment);
if (assignment?.feature === 'path') {
this.completeImportPath(context, acceptor);
}
else {
return super.completionFor(context, next, acceptor);
}
}
completeImportPath(context, acceptor) {
const text = context.textDocument.getText();
const existingText = text.substring(context.tokenOffset, context.offset);
let allPaths = this.getAllFiles(context.document);
let range = {
start: context.position,
end: context.position
};
if (existingText.length > 0) {
const existingPath = existingText.substring(1);
allPaths = allPaths.filter(path => path.startsWith(existingPath));
// Completely replace the current token
const start = context.textDocument.positionAt(context.tokenOffset + 1);
const end = context.textDocument.positionAt(context.tokenEndOffset - 1);
range = {
start,
end
};
}
for (const path of allPaths) {
// Only insert quotes if there is no `path` token yet.
const delimiter = existingText.length > 0 ? '' : '"';
const completionValue = `${delimiter}${path}${delimiter}`;
acceptor(context, {
label: path,
textEdit: {
newText: completionValue,
range
},
kind: CompletionItemKind.File,
sortText: '0'
});
}
}
getAllFiles(document) {
const documents = this.documents().all;
const uri = document.uri.toString();
const dirname = UriUtils.dirname(document.uri).toString();
const paths = [];
for (const doc of documents) {
if (!UriUtils.equals(doc.uri, uri)) {
const docUri = doc.uri.toString();
const uriWithoutExt = docUri.substring(0, docUri.length - UriUtils.extname(doc.uri).length);
let relativePath = UriUtils.relative(dirname, uriWithoutExt);
if (!relativePath.startsWith('.')) {
relativePath = `./${relativePath}`;
}
paths.push(relativePath);
}
}
return paths;
}
}
//# sourceMappingURL=grammar-completion-provider.js.map
@@ -0,0 +1 @@
{"version":3,"file":"grammar-completion-provider.js","sourceRoot":"","sources":["../../../src/grammar/lsp/grammar-completion-provider.ts"],"names":[],"mappings":"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"}
+20
View File
@@ -0,0 +1,20 @@
/******************************************************************************
* 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 type { DefinitionParams } from 'vscode-languageserver';
import type { LangiumServices } from '../../lsp/lsp-services.js';
import type { AstNode, LeafCstNode } from '../../syntax-tree.js';
import type { MaybePromise } from '../../utils/promise-utils.js';
import type { LangiumDocuments } from '../../workspace/documents.js';
import type { Grammar } from '../../languages/generated/ast.js';
import { LocationLink } from 'vscode-languageserver';
import { DefaultDefinitionProvider } from '../../lsp/index.js';
export declare class LangiumGrammarDefinitionProvider extends DefaultDefinitionProvider {
protected documents: LangiumDocuments;
constructor(services: LangiumServices);
protected collectLocationLinks(sourceCstNode: LeafCstNode, _params: DefinitionParams): MaybePromise<LocationLink[] | undefined>;
protected findTargetObject(importedGrammar: Grammar): AstNode | undefined;
}
//# sourceMappingURL=grammar-definition.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"grammar-definition.d.ts","sourceRoot":"","sources":["../../../src/grammar/lsp/grammar-definition.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAEhF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,KAAK,EAAE,OAAO,EAAE,WAAW,EAAc,MAAM,sBAAsB,CAAC;AAC7E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AACjE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AACrE,OAAO,KAAK,EAAE,OAAO,EAAiB,MAAM,kCAAkC,CAAC;AAC/E,OAAO,EAAE,YAAY,EAAS,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AAM/D,qBAAa,gCAAiC,SAAQ,yBAAyB;IAE3E,SAAS,CAAC,SAAS,EAAE,gBAAgB,CAAC;gBAE1B,QAAQ,EAAE,eAAe;cAKlB,oBAAoB,CAAC,aAAa,EAAE,WAAW,EAAE,OAAO,EAAE,gBAAgB,GAAG,YAAY,CAAC,YAAY,EAAE,GAAG,SAAS,CAAC;IAsBxI,SAAS,CAAC,gBAAgB,CAAC,eAAe,EAAE,OAAO,GAAG,OAAO,GAAG,SAAS;CAO5E"}
+41
View File
@@ -0,0 +1,41 @@
/******************************************************************************
* 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 { LocationLink, Range } from 'vscode-languageserver';
import { DefaultDefinitionProvider } from '../../lsp/index.js';
import { streamContents } from '../../utils/ast-utils.js';
import { findAssignment } from '../../utils/grammar-utils.js';
import { isGrammarImport } from '../../languages/generated/ast.js';
import { resolveImport } from '../internal-grammar-util.js';
export class LangiumGrammarDefinitionProvider extends DefaultDefinitionProvider {
constructor(services) {
super(services);
this.documents = services.shared.workspace.LangiumDocuments;
}
collectLocationLinks(sourceCstNode, _params) {
const pathFeature = 'path';
if (isGrammarImport(sourceCstNode.astNode) && findAssignment(sourceCstNode)?.feature === pathFeature) {
const importedGrammar = resolveImport(this.documents, sourceCstNode.astNode);
if (importedGrammar?.$document) {
const targetObject = this.findTargetObject(importedGrammar) ?? importedGrammar;
const selectionRange = this.nameProvider.getNameNode(targetObject)?.range ?? Range.create(0, 0, 0, 0);
const previewRange = targetObject.$cstNode?.range ?? Range.create(0, 0, 0, 0);
return [
LocationLink.create(importedGrammar.$document.uri.toString(), previewRange, selectionRange, sourceCstNode.range)
];
}
return undefined;
}
return super.collectLocationLinks(sourceCstNode, _params);
}
findTargetObject(importedGrammar) {
// Jump to grammar name or the first element
if (importedGrammar.isDeclared) {
return importedGrammar;
}
return streamContents(importedGrammar).head();
}
}
//# sourceMappingURL=grammar-definition.js.map
@@ -0,0 +1 @@
{"version":3,"file":"grammar-definition.js","sourceRoot":"","sources":["../../../src/grammar/lsp/grammar-definition.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAQhF,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AAC/D,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AACnE,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAE5D,MAAM,OAAO,gCAAiC,SAAQ,yBAAyB;IAI3E,YAAY,QAAyB;QACjC,KAAK,CAAC,QAAQ,CAAC,CAAC;QAChB,IAAI,CAAC,SAAS,GAAG,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC,gBAAgB,CAAC;IAChE,CAAC;IAEkB,oBAAoB,CAAC,aAA0B,EAAE,OAAyB;QACzF,MAAM,WAAW,GAA8B,MAAM,CAAC;QACtD,IAAI,eAAe,CAAC,aAAa,CAAC,OAAO,CAAC,IAAI,cAAc,CAAC,aAAa,CAAC,EAAE,OAAO,KAAK,WAAW,EAAE,CAAC;YACnG,MAAM,eAAe,GAAG,aAAa,CAAC,IAAI,CAAC,SAAS,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;YAC7E,IAAI,eAAe,EAAE,SAAS,EAAE,CAAC;gBAC7B,MAAM,YAAY,GAAG,IAAI,CAAC,gBAAgB,CAAC,eAAe,CAAC,IAAI,eAAe,CAAC;gBAC/E,MAAM,cAAc,GAAG,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC,YAAY,CAAC,EAAE,KAAK,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;gBACtG,MAAM,YAAY,GAAG,YAAY,CAAC,QAAQ,EAAE,KAAK,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;gBAC9E,OAAO;oBACH,YAAY,CAAC,MAAM,CACf,eAAe,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,EAAE,EACxC,YAAY,EACZ,cAAc,EACd,aAAa,CAAC,KAAK,CACtB;iBACJ,CAAC;YACN,CAAC;YACD,OAAO,SAAS,CAAC;QACrB,CAAC;QACD,OAAO,KAAK,CAAC,oBAAoB,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;IAC9D,CAAC;IAES,gBAAgB,CAAC,eAAwB;QAC/C,4CAA4C;QAC5C,IAAI,eAAe,CAAC,UAAU,EAAE,CAAC;YAC7B,OAAO,eAAe,CAAC;QAC3B,CAAC;QACD,OAAO,cAAc,CAAC,eAAe,CAAC,CAAC,IAAI,EAAE,CAAC;IAClD,CAAC;CACJ"}
@@ -0,0 +1,14 @@
/******************************************************************************
* 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 { AstNode } from '../../syntax-tree.js';
import { DefaultFoldingRangeProvider } from '../../lsp/folding-range-provider.js';
/**
* A specialized folding range provider for the grammar language
*/
export declare class LangiumGrammarFoldingRangeProvider extends DefaultFoldingRangeProvider {
shouldProcessContent(node: AstNode): boolean;
}
//# sourceMappingURL=grammar-folding-ranges.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"grammar-folding-ranges.d.ts","sourceRoot":"","sources":["../../../src/grammar/lsp/grammar-folding-ranges.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAEhF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,2BAA2B,EAAE,MAAM,qCAAqC,CAAC;AAGlF;;GAEG;AACH,qBAAa,kCAAmC,SAAQ,2BAA2B;IAEtE,oBAAoB,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO;CAIxD"}
+17
View File
@@ -0,0 +1,17 @@
/******************************************************************************
* 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 { DefaultFoldingRangeProvider } from '../../lsp/folding-range-provider.js';
import { isParserRule } from '../../languages/generated/ast.js';
/**
* A specialized folding range provider for the grammar language
*/
export class LangiumGrammarFoldingRangeProvider extends DefaultFoldingRangeProvider {
shouldProcessContent(node) {
// Exclude parser rules from folding
return !isParserRule(node);
}
}
//# sourceMappingURL=grammar-folding-ranges.js.map
@@ -0,0 +1 @@
{"version":3,"file":"grammar-folding-ranges.js","sourceRoot":"","sources":["../../../src/grammar/lsp/grammar-folding-ranges.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAGhF,OAAO,EAAE,2BAA2B,EAAE,MAAM,qCAAqC,CAAC;AAClF,OAAO,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAEhE;;GAEG;AACH,MAAM,OAAO,kCAAmC,SAAQ,2BAA2B;IAEtE,oBAAoB,CAAC,IAAa;QACvC,oCAAoC;QACpC,OAAO,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;IAC/B,CAAC;CACJ"}
+11
View File
@@ -0,0 +1,11 @@
/******************************************************************************
* 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 type { AstNode } from '../../syntax-tree.js';
import { AbstractFormatter } from '../../lsp/formatter.js';
export declare class LangiumGrammarFormatter extends AbstractFormatter {
protected format(node: AstNode): void;
}
//# sourceMappingURL=grammar-formatter.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"grammar-formatter.d.ts","sourceRoot":"","sources":["../../../src/grammar/lsp/grammar-formatter.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAEhF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,iBAAiB,EAAc,MAAM,wBAAwB,CAAC;AAKvE,qBAAa,uBAAwB,SAAQ,iBAAiB;IAE1D,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI;CA0FxC"}
+112
View File
@@ -0,0 +1,112 @@
/******************************************************************************
* 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 { AbstractFormatter, Formatting } from '../../lsp/formatter.js';
import * as ast from '../../languages/generated/ast.js';
const indentOrSpace = Formatting.fit(Formatting.oneSpace(), Formatting.indent());
export class LangiumGrammarFormatter extends AbstractFormatter {
format(node) {
if (ast.isCrossReference(node)) {
const formatter = this.getNodeFormatter(node);
formatter.properties('type', 'terminal').surround(Formatting.noSpace());
}
else if (ast.isParserRule(node)) {
const formatter = this.getNodeFormatter(node);
formatter.keywords('entry', 'fragment', 'returns').append(Formatting.oneSpace());
if ((node.inferredType || node.returnType || node.dataType) && node.parameters.length === 0) {
formatter.property('name').append(Formatting.oneSpace());
}
else {
formatter.property('name').append(Formatting.noSpace());
}
formatter.properties('parameters').append(Formatting.noSpace());
formatter.keywords(',').append(Formatting.oneSpace());
formatter.keywords('<').append(Formatting.noSpace());
const semicolon = formatter.keyword(';');
const colon = formatter.keyword(':');
colon.prepend(Formatting.noSpace());
formatter.interior(colon, semicolon).prepend(Formatting.indent());
semicolon.prepend(Formatting.fit(Formatting.noSpace(), Formatting.newLine()));
formatter.node(node).prepend(Formatting.noIndent());
}
else if (ast.isTerminalRule(node)) {
const formatter = this.getNodeFormatter(node);
if (node.type) {
formatter.property('name').append(Formatting.oneSpace());
formatter.keyword('returns').append(Formatting.oneSpace());
}
formatter.keywords('hidden', 'terminal', 'fragment').append(Formatting.oneSpace());
formatter.keyword(':').prepend(Formatting.noSpace());
formatter.keyword(';').prepend(Formatting.fit(Formatting.noSpace(), Formatting.newLine()));
formatter.node(node).prepend(Formatting.noIndent());
}
else if (ast.isAction(node)) {
const formatter = this.getNodeFormatter(node);
formatter.keyword('{').append(Formatting.noSpace());
formatter.keywords('.', '+=', '=').surround(Formatting.noSpace());
formatter.keyword('}').prepend(Formatting.noSpace());
}
else if (ast.isInferredType(node)) {
const formatter = this.getNodeFormatter(node);
formatter.keywords('infer', 'infers').append(Formatting.oneSpace());
}
else if (ast.isAssignment(node)) {
const formatter = this.getNodeFormatter(node);
formatter.keywords('=', '+=', '?=').surround(Formatting.noSpace());
}
else if (ast.isRuleCall(node)) {
const formatter = this.getNodeFormatter(node);
formatter.keyword('<').surround(Formatting.noSpace());
formatter.keyword(',').append(Formatting.oneSpace());
formatter.properties('arguments').append(Formatting.noSpace());
}
else if (ast.isInterface(node)) {
const formatter = this.getNodeFormatter(node);
formatter.keyword('interface').append(Formatting.oneSpace());
formatter.keyword('extends').prepend(Formatting.oneSpace()).append(indentOrSpace);
formatter.keywords(',').prepend(Formatting.noSpace()).append(indentOrSpace);
const bracesOpen = formatter.keyword('{');
bracesOpen.prepend(Formatting.fit(Formatting.oneSpace(), Formatting.newLine()));
const bracesClose = formatter.keyword('}');
bracesClose.prepend(Formatting.newLine());
formatter.interior(bracesOpen, bracesClose).prepend(Formatting.indent());
}
else if (ast.isType(node)) {
const formatter = this.getNodeFormatter(node);
formatter.keyword('type').append(Formatting.oneSpace());
formatter.keyword('=').prepend(Formatting.oneSpace()).append(indentOrSpace);
formatter.keyword(';').prepend(Formatting.noSpace()).append(Formatting.newLine());
}
else if (ast.isGrammar(node)) {
const formatter = this.getNodeFormatter(node);
const nodes = formatter.nodes(...node.rules, ...node.interfaces, ...node.types, ...node.imports);
nodes.prepend(Formatting.noIndent());
formatter.keyword('grammar').prepend(Formatting.noSpace()).append(Formatting.oneSpace());
}
else if (ast.isInfixRuleOperatorList(node)) {
const formatter = this.getNodeFormatter(node);
formatter.keywords('left', 'right').append(Formatting.oneSpace());
formatter.keywords('|').surround(Formatting.oneSpace());
}
else if (ast.isInfixRuleOperators(node)) {
const formatter = this.getNodeFormatter(node);
formatter.keywords('>').prepend(Formatting.newLine());
}
else if (ast.isInfixRule(node)) {
const formatter = this.getNodeFormatter(node);
formatter.keyword('infix').append(Formatting.oneSpace());
formatter.keyword('on').append(Formatting.oneSpace());
const semicolon = formatter.keyword(';');
const colon = formatter.keyword(':');
colon.prepend(Formatting.noSpace());
formatter.interior(colon, semicolon).prepend(Formatting.indent());
}
if (ast.isAbstractElement(node)) {
const formatter = this.getNodeFormatter(node);
formatter.property('cardinality').prepend(Formatting.noSpace());
}
}
}
//# sourceMappingURL=grammar-formatter.js.map
File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More