+967
@@ -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
|
||||
+1
File diff suppressed because one or more lines are too long
+386
@@ -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
|
||||
+1
File diff suppressed because one or more lines are too long
+40
@@ -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
@@ -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
@@ -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
|
||||
+1
File diff suppressed because one or more lines are too long
+13
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"node-joiner.d.ts","sourceRoot":"","sources":["../../src/generate/node-joiner.ts"],"names":[],"mappings":"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"}
|
||||
+67
@@ -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
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"node-joiner.js","sourceRoot":"","sources":["../../src/generate/node-joiner.ts"],"names":[],"mappings":"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"}
|
||||
+12
@@ -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
@@ -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
@@ -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
|
||||
+1
File diff suppressed because one or more lines are too long
+280
@@ -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
@@ -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
@@ -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
|
||||
+1
File diff suppressed because one or more lines are too long
+29
@@ -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
@@ -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
@@ -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
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"template-string.js","sourceRoot":"","sources":["../../src/generate/template-string.ts"],"names":[],"mappings":"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"}
|
||||
Reference in New Issue
Block a user