First commit.

Signed-off-by: Chen Xiao <abigwc@gmail.com>
This commit is contained in:
Chen Xiao
2026-05-08 14:43:16 +08:00
commit 0b64e2de94
10989 changed files with 2253791 additions and 0 deletions
+967
View File
@@ -0,0 +1,967 @@
/******************************************************************************
* Copyright 2021 TypeFox GmbH
* This program and the accompanying materials are made available under the
* terms of the MIT License, which is available in the project root.
******************************************************************************/
import type { AstNode, Properties } from '../syntax-tree.js';
import type { SourceRegion, TraceRegion, TraceSourceSpec } from './generator-tracing.js';
export declare const EOL: string;
/**
* Common type of expected results of functions contributing to code generation.
* Includes `undefined` for allowing contributing functions to explicitly contribute
* nothing, if required, in contrast to contributing empty strings,
* which facilitates better formatting of the desired output, for example.
*/
export type Generated = GeneratorNode | string | undefined;
export type GeneratorNode = CompositeGeneratorNode | IndentNode | NewLineNode;
export interface IndentConfig {
indentedChildren?: Generated[] | ((indented: IndentNode) => void);
indentation?: string | number;
indentEmptyLines?: boolean;
indentImmediately?: boolean;
}
export declare function isGeneratorNode(node: unknown): node is CompositeGeneratorNode | IndentNode | NewLineNode;
export declare function isNewLineNode(node: unknown): node is NewLineNode;
/**
* Converts instances of {@link GeneratorNode} into a `string`, defaults to {@link String String(...)} for any other `input`.
*
* @param defaultIndentation the indentation to be applied if no explicit indentation is configured
* for particular {@link IndentNode IndentNodes}, either a `string` or a `number` of repeated single spaces,
* defaults to 4 single spaces, see {@link processGeneratorNode} -> `Context`.
*
* @returns the plain `string` represented by the given input.
*/
export declare function toString(input: unknown, defaultIndentation?: string | number): string;
/**
* Converts instances of {@link GeneratorNode} into `text` accompanied by a corresponding `trace`.
*
* @param defaultIndentation the indentation to be applied if no explicit indentation is configured
* for particular {@link IndentNode IndentNodes}, either a `string` or a `number` of repeated single spaces,
* defaults to 4 single spaces, see {@link processGeneratorNode} -> `Context`.
*
* @returns an object of type `{ text: string, trace: TraceRegion }` containing the desired `text` and `trace` data
*/
export declare function toStringAndTrace(input: GeneratorNode, defaultIndentation?: string | number): {
text: string;
trace: TraceRegion;
};
/**
* Implementation of {@link GeneratorNode} serving as container for `string` segments, {@link NewLineNode newline indicators},
* and further {@link CompositeGeneratorNode CompositeGeneratorNodes}, esp. {@link IndentNode IndentNodes}.
*
* See usage examples in the `append...` methods' documentations for details.
*/
export declare class CompositeGeneratorNode {
readonly contents: Array<(GeneratorNode | string)>;
tracedSource?: TraceSourceSpec | SourceRegion | SourceRegion[];
/**
* Constructor.
*
* @param content a var arg mixture of `strings` and {@link GeneratorNode GeneratorNodes}
* describing the initial content of this {@link CompositeGeneratorNode}
*
* @example
* new CompositeGeneratorNode(
* 'Hello World!', NL
* );
*/
constructor(...content: Generated[]);
isEmpty(): boolean;
/**
* Adds tracing information in form of `{astNode, property?, index: undefined}` to `this` generator node.
* Overwrites existing trace data, if set previously.
*
* The given data are kept as they are, the actual resolution of text positions within the DSL text
* is done at the final processing of `this` node as part of {@link toStringAndTrace()}.
*
* @param astNode the AstNode corresponding to `this` node's content
*
* @param property the value property name (string) corresponding to `this` node's content,
* e.g. if this node's content corresponds to some `string` or `number` property; is optional
*
* @returns `this` {@link CompositeGeneratorNode} for convenience.
*/
trace<T extends AstNode>(astNode: T, property?: Properties<T>): this;
/**
* Adds tracing information in form of `{astNode, property, index}` to `this` generator node.
* Overwrites existing trace data, if set previously.
*
* The given data are kept as they are, the actual resolution of text positions within the DSL text
* is done at the final processing of `this` node as part of {@link toStringAndTrace()}.
*
* @param astNode the AstNode corresponding to `this` node's content
*
* @param property the value property name (string) corresponding to `this` node's content,
* e.g. if this node's content corresponds to some `string` or `number` property
*
* @param index the index of the value within a list property corresponding to `this` node's content,
* if the property contains a list of elements, is ignored otherwise
*
* @returns `this` {@link CompositeGeneratorNode} for convenience.
*/
trace<T extends AstNode>(astNode: T, property: Properties<T>, index: number | undefined): this;
/**
* Adds tracing information in form of concrete coordinates to `this` generator node. Complete coordinates
* are provided by the {@link AstNode AstNodes}' corresponding {@link AstNode.$cstNode AstNode.$cstNodes}.
* Overwrites existing trace data, if set previously.
*
* @param sourceRegion a text region within some file in form of concrete coordinates,
* if `undefined` no tracing will happen
*
* @returns `this` {@link CompositeGeneratorNode} for convenience.
*/
trace(sourceRegion: SourceRegion | undefined): this;
/**
* Adds tracing information in form of a list of concrete coordinates to `this` generator node.
* Overwrites existing trace data, if set previously.
*
* The list of regions in `sourceRegion` is reduced to the smallest encompassing region
* of all the contained source regions.
*
* @param sourceRegion a list of text regions within some file in form of concrete coordinates,
* if empty no tracing will happen
*
* @returns `this` {@link CompositeGeneratorNode} for convenience.
*/
trace(sourceRegion: SourceRegion[]): this;
/**
* Appends `strings` and instances of {@link GeneratorNode} to `this` generator node.
*
* @param content a var arg mixture of `strings`, {@link GeneratorNode GeneratorNodes}, or single param
* functions that are immediately called with `this` node as argument, and which may append elements themselves.
*
* @returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().append(
* 'Hello', ' ', `${name}!`, NL, someOtherNode, 'NL', node => generateContent(node)
* ).append(
* 'The end!'
* );
*/
append(...content: Array<Generated | ((node: this) => void)>): this;
/**
* Prepends `strings` and instances of {@link GeneratorNode} to the content of `this` generator node.
*
* @param content a var arg mixture of `strings` or {@link GeneratorNode GeneratorNodes}.
*
* @returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* generateSomeContent()?.prepend(
* 'Some preamble text:', NL
* ).append(
* 'Some postamble text:', NL
* );
*/
prepend(...content: Generated[]): this;
/**
* Appends `strings` and instances of {@link GeneratorNode} to `this` generator node, if `condition` is equal to `true`.
*
* If `condition` is satisfied this method delegates to {@link append}, otherwise it returns just `this`.
*
* @param condition a boolean value indicating whether to append the elements of `args` to `this`.
*
* @param content a var arg mixture of `strings`, {@link GeneratorNode GeneratorNodes}, or single param
* functions that are immediately called with `this` node as argument, and which may append elements themselves.
*
* @returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().append(
* 'Hello World!'
* ).appendNewLine().appendIf(
* entity !== undefined, `Hello ${entity?.name}!`
* ).appendNewLineIfNotEmpty();
*/
appendIf(condition: boolean, ...content: Array<Generated | ((node: CompositeGeneratorNode) => void)>): this;
/**
* Prepends `strings` and instances of {@link GeneratorNode} to the content of `this` generator node, if `condition` is equal to `true`.
*
* If `condition` is satisfied this method delegates to {@link prepend}, otherwise it returns just `this`.
*
* @param condition a boolean value indicating whether to prepend the elements of `args` to the content of `this`.
*
* @param content a var arg mixture of `strings` or {@link GeneratorNode GeneratorNodes}.
*
* @returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* generateSomeContent()?.prependIf(
* generatePreamble === true,
* 'Some preamble', NL
* ).appendIf(
* generatePostamble === true,
* 'Some postamble', NL
* );
*/
prependIf(condition: boolean, ...content: Generated[]): this;
/**
* Appends a strict {@link NewLineNode} to `this` node.
* Strict {@link NewLineNode}s yield mandatory linebreaks in the derived generated text.
*
* @returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().append(
* 'Hello World!'
* ).appendNewLine();
*/
appendNewLine(): this;
/**
* Appends a strict {@link NewLineNode} to `this` node, if `condition` is equal to `true`.
* Strict {@link NewLineNode}s yield mandatory linebreaks in the derived generated text.
*
* @param condition a boolean value indicating whether to append a {@link NewLineNode} to `this`.
*
* @returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().append(
* 'Hello World!'
* ).appendNewLineIf(entity !== undefined).appendIf(
* entity !== undefined, `Hello ${entity?.name}!`
* )
*/
appendNewLineIf(condition: boolean): this;
/**
* Appends a soft {@link NewLineNode} to `this` node.
* Soft {@link NewLineNode}s yield linebreaks in the derived generated text only if the preceding line is non-empty,
* i.e. there are non-whitespace characters added to the generated text since the last linebreak.
*
* @returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().appendIf(
* entity !== undefined, `Hello ${entity?.name}!`
* ).appendNewLineIfNotEmpty();
*/
appendNewLineIfNotEmpty(): this;
/**
* Appends a soft {@link NewLineNode} to `this` node, if `condition` is equal to `true`.
* Soft {@link NewLineNode}s yield linebreaks in the derived generated text only if the preceding line is non-empty,
* i.e. there are non-whitespace characters added to the generated text since the last linebreak.
*
* @param condition a boolean value indicating whether to append a {@link NewLineNode} to `this`.
*
* @returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().append(
* entity.label ?? ''
* ).appendNewLineIfNotEmptyIf(entity.description !== undefined).append(
* entity.description
* )
*/
appendNewLineIfNotEmptyIf(condition: boolean): this;
/**
* Convenience method for appending content in form of a template to `this` generation node.
*
* See {@link expandToNode} for details.
*
* @returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().appendTemplate
* `Hello World!`
* .appendNewLine()
*/
appendTemplate(staticParts: TemplateStringsArray, ...substitutions: unknown[]): this;
/**
* Convenience method for appending content in form of a template to `this` generator node, if `condition` is equal to `true`.
*
* This method returns a tag function that takes the desired template and does the processing.
*
* If `condition` is satisfied the tagged template delegates to {@link appendTemplate}, otherwise it returns just `this`.
*
* See {@link expandToNode} for details.
*
* @param condition a boolean value indicating whether to append the template content to `this`.
*
* @returns a tag function behaving as described above, which in turn returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().appendTemplate
* `Hello World!`
* .appendNewLine().appendTemplateIf(entity !== undefined)
* `Hello ${entity?.name}!`
* .appendNewLineIfNotEmpty()
*/
appendTemplateIf(condition: boolean): (staticParts: TemplateStringsArray, ...substitutions: unknown[]) => this;
/**
* Adds an area of indented text output.
* The content to be indented can be provided as an array consisting of strings and/or generation nodes
* (undefined is permitted), or via a callback offering the `indentingNode` to which the content shall be appended.
* Alternatively, an object satisfying {@link IndentConfig} can be provided taking the children as Array or via
* a callback as described previously via the `indentedChildren` property.
*
* The remaining properties of {@link IndentConfig} have the following effects:
* - `indentation`: a specific indentation length or string, defaults to the global indentation setting if omitted, see {@link toString},
* - `indentEmptyLines`: apply indentation to empty lines, defaults to `false`
* - `indentImmediately`: apply the indentation immediately starting at the first line, defaults to `true`, might be set to `false`
* if preceding content is not terminated by any `newline`. If `false` the indentation is inserted only after child `newline` nodes
* followed by further content.
*
* @param childrenOrConfig an {@link Array} or callback contributing the children, or a config object satisfying {@link IndentConfig} alternatively.
*
* @returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().append(
* '{'
* ).indent(indentingNode =>
* indentingNode.append(
* 'name:', name, ','
* ).appendNewLine().appendIf(description !== undefined,
* 'description:', description
* ).appendNewLineIfNotEmpty()
* ).append(
* '}'
* );
*/
indent(childrenOrConfig?: Generated[] | ((indented: IndentNode) => void) | IndentConfig): this;
/**
* Convenience method for appending content to `this` generator node including tracing information
* in form of `{astNode, property?, index: undefined}`.
*
* This method returns a helper function that takes the desired `content` and does the processing.
* The returned function delegates to {@link append}, with the provided `content` being
* wrapped by an additional {@link CompositeGeneratorNode} configured with the tracing information.
*
* @param astNode the AstNode corresponding to the appended content
*
* @param property the value property name (string) corresponding to the appended content,
* if e.g. the content corresponds to some `string` or `number` property of `astNode`, is optional
*
* @returns a function behaving as described above, which in turn returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().appendTemplate
* `Hello World!`
* .appendNewLine().append('Hello ').appendTraced(entity, 'name')(entity.name)
* .appendNewLineIfNotEmpty()
*/
appendTraced<T extends AstNode>(astNode: T, property?: Properties<T>): (...content: Array<Generated | ((node: CompositeGeneratorNode) => void)>) => this;
/**
* Convenience method for appending content to `this` generator node including tracing information
* in form of `{astNode, property, index}`.
*
* This method returns a helper function that takes the desired `content` and does the processing.
* The returned function delegates to {@link append}, with the provided `content` being
* wrapped by an additional {@link CompositeGeneratorNode} configured with the tracing information.
*
* @param astNode the AstNode corresponding to the appended content
*
* @param property the value property name (string) corresponding to the appended content,
* if e.g. the content corresponds to some `string` or `number` property of `astNode`
*
* @param index the index of the value within a list property corresponding to the appended content,
* if the property contains a list of elements, is ignored otherwise
*
* @returns a function behaving as described above, which in turn returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().appendTemplate
* `Hello World!`
* .appendNewLine().append('Hello ').appendTraced(entity, 'name')(entity.name)
* .appendNewLineIfNotEmpty()
*/
appendTraced<T extends AstNode>(astNode: T, property: Properties<T>, index: number | undefined): (...content: Array<Generated | ((node: CompositeGeneratorNode) => void)>) => this;
/**
* Convenience method for appending content to `this` generator node including tracing information
* in form of concrete coordinates.
*
* This method returns a helper function that takes the desired `content` and does the processing.
* The returned function delegates to {@link append}, with the provided `content` being
* wrapped by an additional {@link CompositeGeneratorNode} configured with the tracing information.
*
* @param sourceRegion a text region within some file in form of concrete coordinates,
* if `undefined` no tracing will happen
*
* @returns a function behaving as described above, which in turn returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().appendTemplate
* `Hello World!`
* .appendNewLine().append('Hello ').appendTraced(findNodeForProperty(entity.$cstNode, 'name'))(entity.name)
* .appendNewLineIfNotEmpty()
*/
appendTraced(sourceRegion: SourceRegion | undefined): (...content: Array<Generated | ((node: CompositeGeneratorNode) => void)>) => this;
/**
* Convenience method for appending content to `this` generator node including tracing information
* in form of a list of concrete coordinates.
*
* This method returns a helper function that takes the desired `content` and does the processing.
* The returned function delegates to {@link append}, with the provided `content` being
* wrapped by an additional {@link CompositeGeneratorNode} configured with the tracing information.
*
* The list of regions in `sourceRegions` will later be reduced to the smallest encompassing region
* of all the contained source regions.
*
* @param sourceRegions a list of text regions within some file in form of concrete coordinates,
* if empty no tracing will happen
*
* @returns a function behaving as described above, which in turn returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().appendTemplate
* `Hello World!`
* .appendNewLine().append('Hello ').appendTraced([ findNodeForProperty(entity.$cstNode, 'name') ])(entity.name)
* .appendNewLineIfNotEmpty()
*/
appendTraced(sourceRegions: SourceRegion[]): (...content: Array<Generated | ((node: CompositeGeneratorNode) => void)>) => this;
/**
* Convenience method for appending content to `this` generator node including tracing information
* in form of `{astNode, property?, index: undefined}`, if `condition` is equal to `true`.
*
* This method returns a tag function that takes the desired template and does the processing.
*
* If `condition` is satisfied the returned function delegates to {@link appendTraced}, otherwise it returns just `this`.
*
* @param condition a boolean value indicating whether to append the template content to `this`.
*
* @param astNode the AstNode corresponding to the appended content
*
* @param property the value property name (string) corresponding to the appended content,
* if e.g. the content corresponds to some `string` or `number` property of `astNode`, is optional
*
* @returns a function behaving as described above, which in turn returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().appendTemplate
* `Hello World!`
* .appendNewLine().appendIf(entity !== undefined, 'Hello ').appendTracedIf(entity !== undefined, entity, 'name')(entity?.name)
* .appendNewLineIfNotEmpty()
*/
appendTracedIf<T extends AstNode>(condition: boolean, astNode: T, property?: Properties<T>): (...content: Array<Generated | ((node: CompositeGeneratorNode) => void)>) => this;
/**
* Convenience method for appending content to `this` generator node including tracing information
* in form of `{astNode, property, index}`, if `condition` is equal to `true`.
*
* This method returns a tag function that takes the desired template and does the processing.
*
* If `condition` is satisfied the returned function delegates to {@link appendTraced}, otherwise it returns just `this`.
*
* @param condition a boolean value indicating whether to append the template content to `this`.
*
* @param astNode the AstNode corresponding to the appended content
*
* @param property the value property name (string) corresponding to the appended content,
* if e.g. the content corresponds to some `string` or `number` property of `astNode`
*
* @param index the index of the value within a list property corresponding to the appended content,
* if the property contains a list of elements, is ignored otherwise
*
* @returns a function behaving as described above, which in turn returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().appendTemplate
* `Hello World!`
* .appendNewLine().appendIf(entity !== undefined, 'Hello ').appendTracedIf(entity !== undefined, entity, 'name')(entity?.name)
* .appendNewLineIfNotEmpty()
*/
appendTracedIf<T extends AstNode>(condition: boolean, astNode: T, property: Properties<T>, index: number | undefined): (...content: Array<Generated | ((node: CompositeGeneratorNode) => void)>) => this;
/**
* Convenience method for appending content to `this` generator node including tracing information
* in form of concrete coordinates, if `condition` is equal to `true`.
*
* This method returns a tag function that takes the desired template and does the processing.
*
* If `condition` is satisfied the returned function delegates to {@link appendTraced}, otherwise it returns just `this`.
*
* If `sourceRegion` is a function supplying the corresponding region, it's only called if `condition` is satisfied.
*
* @param condition a boolean value indicating whether to append the template content to `this`.
*
* @param sourceRegion a text region within some file in form of concrete coordinates,
* if `undefined` no tracing will happen
*
* @returns a function behaving as described above, which in turn returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().appendTemplate
* `Hello World!`
* .appendNewLine().appendIf(entity !== undefined, 'Hello ').appendTracedIf(entity !== undefined, () => findNodeForProperty(entity.$cstNode, 'name'))(entity?.name)
* .appendNewLineIfNotEmpty()
*/
appendTracedIf(condition: boolean, sourceRegion: SourceRegion | undefined | (() => SourceRegion | undefined)): (...content: Array<Generated | ((node: CompositeGeneratorNode) => void)>) => this;
/**
* Convenience method for appending content to `this` generator node including tracing information
* in form of a list of concrete coordinates, if `condition` is equal to `true`.
*
* This method returns a tag function that takes the desired template and does the processing.
*
* If `condition` is satisfied the returned function delegates to {@link appendTraced}, otherwise it returns just `this`.
*
* The list of regions in `sourceRegions` will later be reduced to the smallest encompassing region
* of all the contained source regions.
* If `sourceRegions` is a function supplying the corresponding regions, it's only called if `condition` is satisfied.
*
* @param condition a boolean value indicating whether to append the template content to `this`.
*
* @param sourceRegions a list of text regions within some file in form of concrete coordinates,
* if empty no tracing will happen
*
* @returns a function behaving as described above, which in turn returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().appendTemplate
* `Hello World!`
* .appendNewLine().appendIf(entity !== undefined, 'Hello ').appendTracedIf(entity !== undefined, () => [ findNodeForProperty(entity.$cstNode, 'name') ])(entity?.name)
* .appendNewLineIfNotEmpty()
*/
appendTracedIf(condition: boolean, sourceRegions: SourceRegion[] | (() => SourceRegion[])): (...content: Array<Generated | ((node: CompositeGeneratorNode) => void)>) => this;
/**
* Convenience method for appending content in form of a template to `this` generator node including tracing
* information in form of `{astNode, property?, index: undefined}`.
*
* This method returns a tag function that takes the desired template and does the processing by delegating to
* {@link expandTracedToNode}.
*
* @param astNode the AstNode corresponding to the appended content
*
* @param property the value property name (string) corresponding to the appended content,
* if e.g. the content corresponds to some `string` or `number` property of `astNode`, is optional
*
* @returns a tag function behaving as described above, which in turn returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().appendTemplate
* `Hello World!`
* .appendNewLine().appendTracedTemplate(entity, 'name')
* `Hello ${entity?.name}!`
* .appendNewLineIfNotEmpty()
*/
appendTracedTemplate<T extends AstNode>(astNode: T, property?: Properties<T>): (staticParts: TemplateStringsArray, ...substitutions: unknown[]) => this;
/**
* Convenience method for appending content in form of a template to `this` generator node including tracing
* information in form of `{astNode, property, index}`.
*
* This method returns a tag function that takes the desired template and does the processing by delegating to
* {@link expandTracedToNode}.
*
* @param astNode the AstNode corresponding to the appended content
*
* @param property the value property name (string) corresponding to the appended content,
* if e.g. the content corresponds to some `string` or `number` property of `astNode`
*
* @param index the index of the value within a list property corresponding to the appended content,
* if the property contains a list of elements, is ignored otherwise, is optinal
*
* @returns a tag function behaving as described above, which in turn returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().appendTemplate
* `Hello World!`
* .appendNewLine().appendTracedTemplate(entity, 'name')
* `Hello ${entity?.name}!`
* .appendNewLineIfNotEmpty()
*/
appendTracedTemplate<T extends AstNode>(astNode: T, property: Properties<T>, index: number | undefined): (staticParts: TemplateStringsArray, ...substitutions: unknown[]) => this;
/**
* Convenience method for appending content in form of a template to `this` generator node including tracing
* information in form of concrete coordinates.
*
* This method returns a tag function that takes the desired template and does the processing by delegating to
* {@link expandTracedToNode}.
*
* @param sourceRegion a text region within some file in form of concrete coordinates,
* if `undefined` no tracing will happen
*
* @returns a tag function behaving as described above, which in turn returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().appendTemplate
* `Hello World!`
* .appendNewLine().appendTracedTemplate(findNodeForProperty(entity.$cstNode, 'name'))
* `Hello ${entity?.name}!`
* .appendNewLineIfNotEmpty()
*/
appendTracedTemplate(sourceRegion: SourceRegion | undefined): (staticParts: TemplateStringsArray, ...substitutions: unknown[]) => this;
/**
* Convenience method for appending content in form of a template to `this` generator node including tracing
* information in form of concrete coordinates.
*
* This method returns a tag function that takes the desired template and does the processing by delegating to
* {@link expandTracedToNode}.
*
* The list of regions in `sourceRegions` will later be reduced to the smallest encompassing region
* of all the contained source regions.
*
* @param sourceRegions a list of text regions within some file in form of concrete coordinates,
* if empty no tracing will happen
*
* @returns a tag function behaving as described above, which in turn returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().appendTemplate
* `Hello World!`
* .appendNewLine().appendTracedTemplate( findNodesForProperty(entity.$cstNode, 'name'))
* `Hello ${entity?.name}!`
* .appendNewLineIfNotEmpty()
*/
appendTracedTemplate(sourceRegions: SourceRegion[]): (staticParts: TemplateStringsArray, ...substitutions: unknown[]) => this;
/**
* Convenience method for appending content in form of a template to `this` generator node including tracing information
* in form of `{astNode, property?, index: undefined}`, if `condition` is equal to `true`.
*
* This method returns a tag function that takes the desired template and does the processing.
*
* If `condition` is satisfied the tagged template delegates to {@link appendTracedTemplate}, otherwise it returns just `this`.
* See also {@link expandTracedToNode} for details.
*
* @param condition a boolean value indicating whether to append the template content to `this`.
*
* @param astNode the AstNode corresponding to the appended content
*
* @param property the value property name (string) corresponding to the appended content,
* if e.g. the content corresponds to some `string` or `number` property of `astNode`, is optional
*
* @returns a tag function behaving as described above, which in turn returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().appendTemplate
* `Hello World!`
* .appendNewLine().appendTracedTemplateIf(entity?.name !== undefined, entity)
* `Hello ${entity?.name}!`
* .appendNewLineIfNotEmpty()
*/
appendTracedTemplateIf<T extends AstNode>(condition: boolean, astNode: T, property?: Properties<T>): (staticParts: TemplateStringsArray, ...substitutions: unknown[]) => this;
/**
* Convenience method for appending content in form of a template to `this` generator node including tracing information
* in form of `{astNode, property, index}`, if `condition` is equal to `true`.
*
* This method returns a tag function that takes the desired template and does the processing.
*
* If `condition` is satisfied the tagged template delegates to {@link appendTracedTemplate}, otherwise it returns just `this`.
* See also {@link expandTracedToNode} for details.
*
* @param condition a boolean value indicating whether to append the template content to `this`.
*
* @param astNode the AstNode corresponding to the appended content
*
* @param property the value property name (string) corresponding to the appended content,
* if e.g. the content corresponds to some `string` or `number` property of `astNode`
*
* @param index the index of the value within a list property corresponding to the appended content,
* if the property contains a list of elements, is ignored otherwise
*
* @returns a tag function behaving as described above, which in turn returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().appendTemplate
* `Hello World!`
* .appendNewLine().appendTracedTemplateIf(entity?.name !== undefined, entity)
* `Hello ${entity?.name}!`
* .appendNewLineIfNotEmpty()
*/
appendTracedTemplateIf<T extends AstNode>(condition: boolean, astNode: T, property: Properties<T>, index: number | undefined): (staticParts: TemplateStringsArray, ...substitutions: unknown[]) => this;
/**
* Convenience method for appending content in form of a template to `this` generator node including tracing information
* in form of concrete coordinates, if `condition` is equal to `true`.
*
* This method returns a tag function that takes the desired template and does the processing.
*
* If `condition` is satisfied the tagged template delegates to {@link appendTracedTemplate}, otherwise it returns just `this`.
* See also {@link expandTracedToNode} for details.
*
* If `sourceRegion` is a function supplying the corresponding region, it's only called if `condition` is satisfied.
*
* @param condition a boolean value indicating whether to append the template content to `this`.
*
* @param sourceRegion a text region within some file in form of concrete coordinates,
* if `undefined` no tracing will happen
*
* @returns a tag function behaving as described above, which in turn returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().appendTemplate
* `Hello World!`
* .appendNewLine().appendTracedTemplateIf(entity?.name !== undefined, entity.$cstNode)
* `Hello ${entity?.name}!`
* .appendNewLineIfNotEmpty()
*/
appendTracedTemplateIf(condition: boolean, sourceRegion: SourceRegion | undefined | (() => SourceRegion | undefined)): (staticParts: TemplateStringsArray, ...substitutions: unknown[]) => this;
/**
* Convenience method for appending content in form of a template to `this` generator node including tracing information
* in form of a list of concrete coordinates, if `condition` is equal to `true`.
*
* This method returns a tag function that takes the desired template and does the processing.
*
* If `condition` is satisfied the tagged template delegates to {@link appendTracedTemplate}, otherwise it returns just `this`.
* See also {@link expandTracedToNode} for details.
*
* The list of regions in `sourceRegions` will later be reduced to the smallest encompassing region
* of all the contained source regions.
* If `sourceRegions` is a function supplying the corresponding regions, it's only called if `condition` is satisfied.
*
* @param condition a boolean value indicating whether to append the template content to `this`.
*
* @param sourceRegions a list of text regions within some file in form of concrete coordinates,
* if empty no tracing will happen
*
* @returns a tag function behaving as described above, which in turn returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().appendTemplate
* `Hello World!`
* .appendNewLine().appendTracedTemplateIf(entity?.name !== undefined, () => [ findNodeForProperty(entity.$cstNode, 'name')! ])
* `Hello ${entity?.name}!`
* .appendNewLineIfNotEmpty()
*/
appendTracedTemplateIf(condition: boolean, sourceRegions: SourceRegion[] | (() => SourceRegion[])): (staticParts: TemplateStringsArray, ...substitutions: unknown[]) => this;
}
/**
* Convenience function for attaching tracing information to content of type `Generated`,
* in form of `{astNode, property?, index: undefined}`.
*
* This method returns a helper function that takes the desired `content` and does the processing.
* The returned function will create and return a new {@link CompositeGeneratorNode} being initialized
* with the given tracing information and add some `content`, if provided.
*
* Exception: if `content` is already a {@link CompositeGeneratorNode} containing no tracing information,
* that node is enriched with the given tracing information and returned, and no wrapping node is created.
*
* @param astNode the AstNode corresponding to the appended content
*
* @param property the value property name (string) corresponding to the appended content,
* if e.g. the content corresponds to some `string` or `number` property of `astNode`, is optional
*
* @returns a function behaving as described above, which in turn returns a {@link CompositeGeneratorNode}.
*
* @example
* new CompositeGeneratorNode().appendTemplate
* `Hello World!`
* .appendNewLine().appendTracedTemplate(entity)
* `Hello ${ traceToNode(entity, 'name')(entity.name) }`
* .appendNewLineIfNotEmpty()
*/
export declare function traceToNode<T extends AstNode>(astNode: T, property?: Properties<T>): (content?: Generated | ((node: CompositeGeneratorNode) => void)) => CompositeGeneratorNode;
/**
* Convenience function for attaching tracing information to content of type `Generated`,
* in form of `{astNode, property, index}`.
*
* This method returns a helper function that takes the desired `content` and does the processing.
* The returned function will create and return a new {@link CompositeGeneratorNode} being initialized
* with the given tracing information and add some `content`, if provided.
*
* Exception: if `content` is already a {@link CompositeGeneratorNode} containing no tracing information,
* that node is enriched with the given tracing information and returned, and no wrapping node is created.
*
* @param astNode the AstNode corresponding to the appended content
*
* @param property the value property name (string) corresponding to the appended content,
* if e.g. the content corresponds to some `string` or `number` property of `astNode`
*
* @param index the index of the value within a list property corresponding to the appended content,
* if the property contains a list of elements, is ignored otherwise
*
* @returns a function behaving as described above, which in turn returns a {@link CompositeGeneratorNode}.
*
* @example
* new CompositeGeneratorNode().appendTemplate
* `Hello World!`
* .appendNewLine().appendTracedTemplate(entity)
* `Hello ${ traceToNode(entity, 'name')(entity.name) }`
* .appendNewLineIfNotEmpty()
*/
export declare function traceToNode<T extends AstNode>(astNode: T, property: Properties<T>, index: number | undefined): (content?: Generated | ((node: CompositeGeneratorNode) => void)) => CompositeGeneratorNode;
/**
* Convenience function for attaching tracing information to content of type `Generated`,
* in form of concrete coordinates.
*
* This method returns a helper function that takes the desired `content` and does the processing.
* The returned function will create and return a new {@link CompositeGeneratorNode} being initialized
* with the given tracing information and add some `content`, if provided.
*
* Exception: if `content` is already a {@link CompositeGeneratorNode} containing no tracing information,
* that node is enriched with the given tracing information and returned, and no wrapping node is created.
*
* @param sourceRegion a text region within some file in form of concrete coordinates,
* if `undefined` no tracing will happen
*
* @returns a function behaving as described above, which in turn returns a {@link CompositeGeneratorNode}.
*
* @example
* new CompositeGeneratorNode().appendTemplate
* `Hello World!`
* .appendNewLine().appendTracedTemplate(entity.$cstNode)
* `Hello ${ traceToNode(findNodeForProperty(entity.$cstNode, 'name'))(entity.name) }`
* .appendNewLineIfNotEmpty()
*/
export declare function traceToNode(sourceRegion: SourceRegion | undefined): (content?: Generated | ((node: CompositeGeneratorNode) => void)) => CompositeGeneratorNode;
/**
* Convenience function for attaching tracing information to content of type `Generated`,
* in form of a list of concrete coordinates.
*
* This method returns a helper function that takes the desired `content` and does the processing.
* The returned function will create and return a new {@link CompositeGeneratorNode} being initialized
* with the given tracing information and add some `content`, if provided.
*
* Exception: if `content` is already a {@link CompositeGeneratorNode} containing no tracing information,
* that node is enriched with the given tracing information and returned, and no wrapping node is created.
*
* The list of regions in `sourceRegions` will later be reduced to the smallest encompassing region
* of all the contained source regions.
*
* @param sourceRegions a list of text region within some file in form of concrete coordinates,
* if empty no tracing will happen
*
* @returns a function behaving as described above, which in turn returns a {@link CompositeGeneratorNode}.
*
* @example
* new CompositeGeneratorNode().appendTemplate
* `Hello World!`
* .appendNewLine().appendTracedTemplate(entity.$cstNode)
* `Hello ${ traceToNode(findNodesForProperty(entity.$cstNode, 'name'))(entity.name) }`
* .appendNewLineIfNotEmpty()
*/
export declare function traceToNode(sourceRegions: SourceRegion[]): (content?: Generated | ((node: CompositeGeneratorNode) => void)) => CompositeGeneratorNode;
/**
* Convenience function for attaching tracing information to content of type `Generated`,
* in form of `{astNode, property?, index: undefined}`, if `condition` is equal to `true`.
*
* If `condition` is satisfied, this method returns a helper function that takes the desired
* `content` and does the processing. The returned function will create and return a new
* {@link CompositeGeneratorNode} being initialized with the given tracing information and
* add some `content`, if provided. Otherwise, the returned function just returns `undefined`.
*
* Exception: if `content` is already a {@link CompositeGeneratorNode} containing no tracing information,
* that node is enriched with the given tracing information and returned, and no wrapping node is created.
*
* @param condition a boolean value indicating whether to apply the provided tracing information to the desired content.
*
* @param astNode the AstNode corresponding to the appended content
*
* @param property the value property name (string) corresponding to the appended content,
* if e.g. the content corresponds to some `string` or `number` property of `astNode`, is optional
*
* @returns a function behaving as described above, which in turn returns a {@link CompositeGeneratorNode}.
*
* @example
* new CompositeGeneratorNode().appendTemplate
* `Hello World!`
* .appendNewLine().appendTracedTemplate(entity)
* `Hello ${ traceToNodeIf(!!entity.name, entity, 'name')(entity.name) }`
* .appendNewLineIfNotEmpty()
*/
export declare function traceToNodeIf<T extends AstNode>(condition: boolean, astNode: T, property?: Properties<T>): (content?: Generated | ((node: CompositeGeneratorNode) => void)) => CompositeGeneratorNode | undefined;
/**
* Convenience function for attaching tracing information to content of type `Generated`,
* in form of `{astNode, property, index}`, if `condition` is equal to `true`.
*
* If `condition` is satisfied, this method returns a helper function that takes the desired
* `content` and does the processing. The returned function will create and return a new
* {@link CompositeGeneratorNode} being initialized with the given tracing information and
* add some `content`, if provided. Otherwise, the returned function just returns `undefined`.
*
* Exception: if `content` is already a {@link CompositeGeneratorNode} containing no tracing information,
* that node is enriched with the given tracing information and returned, and no wrapping node is created.
*
* @param condition a boolean value indicating whether to apply the provided tracing information to the desired content.
*
* @param astNode the AstNode corresponding to the appended content
*
* @param property the value property name (string) corresponding to the appended content,
* if e.g. the content corresponds to some `string` or `number` property of `astNode`
*
* @param index the index of the value within a list property corresponding to the appended content,
* if the property contains a list of elements, is ignored otherwise
*
* @returns a function behaving as described above, which in turn returns a {@link CompositeGeneratorNode}.
*
* @example
* new CompositeGeneratorNode().appendTemplate
* `Hello World!`
* .appendNewLine().appendTracedTemplate(entity)
* `Hello ${ traceToNodeIf(!!entity.name, entity, 'name')(entity.name) }`
* .appendNewLineIfNotEmpty()
*/
export declare function traceToNodeIf<T extends AstNode>(condition: boolean, astNode: T, property: Properties<T>, index: number | undefined): (content?: Generated | ((node: CompositeGeneratorNode) => void)) => CompositeGeneratorNode | undefined;
/**
* Convenience function for attaching tracing information to content of type `Generated`,
* in form of concrete coordinates, if `condition` is equal to `true`.
*
* If `condition` is satisfied, this method returns a helper function that takes the desired
* `content` and does the processing. The returned function will create and return a new
* {@link CompositeGeneratorNode} being initialized with the given tracing information and
* add some `content`, if provided. Otherwise, the returned function just returns `undefined`.
*
* Exception: if `content` is already a {@link CompositeGeneratorNode} containing no tracing information,
* that node is enriched with the given tracing information and returned, and no wrapping node is created.
*
* If `sourceRegions` is a function supplying the corresponding regions, it's only called if `condition` is satisfied.
*
* @param condition a boolean value indicating whether to apply the provided tracing information to the desired content.
*
* @param sourceRegion a text region within some file in form of concrete coordinates,
* if `undefined` no tracing will happen
*
* @returns a function behaving as described above, which in turn returns a {@link CompositeGeneratorNode}.
*
* @example
* new CompositeGeneratorNode().appendTemplate
* `Hello World!`
* .appendNewLine().appendTracedTemplate(entity.$cstNode)
* `Hello ${ traceToNodeIf(!!entity.name, () => findNodeForProperty(entity.$cstNode, 'name'))(entity.name) }`
* .appendNewLineIfNotEmpty()
*/
export declare function traceToNodeIf(condition: boolean, sourceRegion: SourceRegion | undefined | (() => SourceRegion | undefined)): (content?: Generated | ((node: CompositeGeneratorNode) => void)) => CompositeGeneratorNode | undefined;
/**
* Convenience function for attaching tracing information to content of type `Generated`,
* in form of a list of concrete coordinates, if `condition` is equal to `true`.
*
* If `condition` is satisfied, this method returns a helper function that takes the desired
* `content` and does the processing. The returned function will create and return a new
* {@link CompositeGeneratorNode} being initialized with the given tracing information and
* add some `content`, if provided. Otherwise, the returned function just returns `undefined`.
*
* Exception: if `content` is already a {@link CompositeGeneratorNode} containing no tracing information,
* that node is enriched with the given tracing information and returned, and no wrapping node is created.
*
* The list of regions in `sourceRegions` will later be reduced to the smallest encompassing region
* of all the contained source regions.
* If `sourceRegions` is a function supplying the corresponding regions, it's only called if `condition` is satisfied.
*
* @param condition a boolean value indicating whether to apply the provided tracing information to the desired content.
*
* @param sourceRegions a list of text region within some file in form of concrete coordinates,
* if empty no tracing will happen
*
* @returns a function behaving as described above, which in turn returns a {@link CompositeGeneratorNode}.
*
* @example
* new CompositeGeneratorNode().appendTemplate
* `Hello World!`
* .appendNewLine().appendTracedTemplate(entity.$cstNode)
* `Hello ${ traceToNodeIf(!!entity.name, () => findNodesForProperty(entity.$cstNode, 'name'))(entity.name) }`
* .appendNewLineIfNotEmpty()
*/
export declare function traceToNodeIf(condition: boolean, sourceRegions: SourceRegion[] | (() => SourceRegion[])): (content?: Generated | ((node: CompositeGeneratorNode) => void)) => CompositeGeneratorNode | undefined;
/**
* Implementation of @{link GeneratorNode} denoting areas within the desired generated text of common increased indentation.
*/
export declare class IndentNode extends CompositeGeneratorNode {
readonly indentation?: string;
readonly indentImmediately: boolean;
readonly indentEmptyLines: boolean;
constructor(indentation?: string | number, indentImmediately?: boolean, indentEmptyLines?: boolean);
}
export declare namespace NewLineNode {
/**
* Integer range type allowing to specify 1 to 6 consecutive line breaks, i.e. up to 5 empty lines with a single `NewLineNode`.
*/
type NoOfLineBreaks = 1 | 2 | 3 | 4 | 5 | 6;
}
/**
* Implementation of @{link GeneratorNode} denoting linebreaks in the desired generated text.
*/
export declare class NewLineNode {
readonly lineDelimiter: string;
readonly ifNotEmpty: boolean;
readonly count: NewLineNode.NoOfLineBreaks;
constructor(lineDelimiter?: string, ifNotEmpty?: boolean, count?: NewLineNode.NoOfLineBreaks);
}
export declare const NL: NewLineNode;
export declare const NLEmpty: NewLineNode;
//# sourceMappingURL=generator-node.d.ts.map
File diff suppressed because one or more lines are too long
+386
View File
@@ -0,0 +1,386 @@
/******************************************************************************
* Copyright 2021 TypeFox GmbH
* This program and the accompanying materials are made available under the
* terms of the MIT License, which is available in the project root.
******************************************************************************/
import { isAstNode } from '../syntax-tree.js';
import { processGeneratorNode } from './node-processor.js';
import { expandToNode, expandTracedToNode } from './template-node.js';
export const EOL = (typeof process === 'undefined') ? '\n' : (process.platform === 'win32') ? '\r\n' : '\n';
export function isGeneratorNode(node) {
return node instanceof CompositeGeneratorNode
|| node instanceof IndentNode
|| node instanceof NewLineNode;
}
export function isNewLineNode(node) {
return node instanceof NewLineNode;
}
/**
* Converts instances of {@link GeneratorNode} into a `string`, defaults to {@link String String(...)} for any other `input`.
*
* @param defaultIndentation the indentation to be applied if no explicit indentation is configured
* for particular {@link IndentNode IndentNodes}, either a `string` or a `number` of repeated single spaces,
* defaults to 4 single spaces, see {@link processGeneratorNode} -> `Context`.
*
* @returns the plain `string` represented by the given input.
*/
export function toString(input, defaultIndentation) {
if (isGeneratorNode(input))
return processGeneratorNode(input, defaultIndentation).text;
else
return String(input);
}
/**
* Converts instances of {@link GeneratorNode} into `text` accompanied by a corresponding `trace`.
*
* @param defaultIndentation the indentation to be applied if no explicit indentation is configured
* for particular {@link IndentNode IndentNodes}, either a `string` or a `number` of repeated single spaces,
* defaults to 4 single spaces, see {@link processGeneratorNode} -> `Context`.
*
* @returns an object of type `{ text: string, trace: TraceRegion }` containing the desired `text` and `trace` data
*/
export function toStringAndTrace(input, defaultIndentation) {
return processGeneratorNode(input, defaultIndentation);
}
/**
* Implementation of {@link GeneratorNode} serving as container for `string` segments, {@link NewLineNode newline indicators},
* and further {@link CompositeGeneratorNode CompositeGeneratorNodes}, esp. {@link IndentNode IndentNodes}.
*
* See usage examples in the `append...` methods' documentations for details.
*/
export class CompositeGeneratorNode {
/**
* Constructor.
*
* @param content a var arg mixture of `strings` and {@link GeneratorNode GeneratorNodes}
* describing the initial content of this {@link CompositeGeneratorNode}
*
* @example
* new CompositeGeneratorNode(
* 'Hello World!', NL
* );
*/
constructor(...content) {
this.contents = [];
this.append(...content);
}
isEmpty() {
return this.contents.length === 0;
}
trace(source, property, index) {
if (isAstNode(source)) {
this.tracedSource = { astNode: source, property, index };
if (this.tracedSource.property === undefined && this.tracedSource.index !== undefined && this.tracedSource.index > -1) {
throw new Error("Generation support: 'property' argument must not be 'undefined' if a non-negative value is assigned to 'index' in 'CompositeGeneratorNode.trace(...)'.");
}
}
else {
this.tracedSource = source;
}
return this;
}
/**
* Appends `strings` and instances of {@link GeneratorNode} to `this` generator node.
*
* @param content a var arg mixture of `strings`, {@link GeneratorNode GeneratorNodes}, or single param
* functions that are immediately called with `this` node as argument, and which may append elements themselves.
*
* @returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().append(
* 'Hello', ' ', `${name}!`, NL, someOtherNode, 'NL', node => generateContent(node)
* ).append(
* 'The end!'
* );
*/
append(...content) {
for (const arg of content) {
if (typeof arg === 'function') {
arg(this);
}
else if (arg) {
this.contents.push(arg);
}
}
return this;
}
/**
* Prepends `strings` and instances of {@link GeneratorNode} to the content of `this` generator node.
*
* @param content a var arg mixture of `strings` or {@link GeneratorNode GeneratorNodes}.
*
* @returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* generateSomeContent()?.prepend(
* 'Some preamble text:', NL
* ).append(
* 'Some postamble text:', NL
* );
*/
prepend(...content) {
this.contents.unshift(...content.filter(c => c !== undefined));
return this;
}
/**
* Appends `strings` and instances of {@link GeneratorNode} to `this` generator node, if `condition` is equal to `true`.
*
* If `condition` is satisfied this method delegates to {@link append}, otherwise it returns just `this`.
*
* @param condition a boolean value indicating whether to append the elements of `args` to `this`.
*
* @param content a var arg mixture of `strings`, {@link GeneratorNode GeneratorNodes}, or single param
* functions that are immediately called with `this` node as argument, and which may append elements themselves.
*
* @returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().append(
* 'Hello World!'
* ).appendNewLine().appendIf(
* entity !== undefined, `Hello ${entity?.name}!`
* ).appendNewLineIfNotEmpty();
*/
appendIf(condition, ...content) {
return condition ? this.append(...content) : this;
}
/**
* Prepends `strings` and instances of {@link GeneratorNode} to the content of `this` generator node, if `condition` is equal to `true`.
*
* If `condition` is satisfied this method delegates to {@link prepend}, otherwise it returns just `this`.
*
* @param condition a boolean value indicating whether to prepend the elements of `args` to the content of `this`.
*
* @param content a var arg mixture of `strings` or {@link GeneratorNode GeneratorNodes}.
*
* @returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* generateSomeContent()?.prependIf(
* generatePreamble === true,
* 'Some preamble', NL
* ).appendIf(
* generatePostamble === true,
* 'Some postamble', NL
* );
*/
prependIf(condition, ...content) {
return condition ? this.prepend(...content) : this;
}
/**
* Appends a strict {@link NewLineNode} to `this` node.
* Strict {@link NewLineNode}s yield mandatory linebreaks in the derived generated text.
*
* @returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().append(
* 'Hello World!'
* ).appendNewLine();
*/
appendNewLine() {
return this.append(NL);
}
/**
* Appends a strict {@link NewLineNode} to `this` node, if `condition` is equal to `true`.
* Strict {@link NewLineNode}s yield mandatory linebreaks in the derived generated text.
*
* @param condition a boolean value indicating whether to append a {@link NewLineNode} to `this`.
*
* @returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().append(
* 'Hello World!'
* ).appendNewLineIf(entity !== undefined).appendIf(
* entity !== undefined, `Hello ${entity?.name}!`
* )
*/
appendNewLineIf(condition) {
return condition ? this.append(NL) : this;
}
/**
* Appends a soft {@link NewLineNode} to `this` node.
* Soft {@link NewLineNode}s yield linebreaks in the derived generated text only if the preceding line is non-empty,
* i.e. there are non-whitespace characters added to the generated text since the last linebreak.
*
* @returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().appendIf(
* entity !== undefined, `Hello ${entity?.name}!`
* ).appendNewLineIfNotEmpty();
*/
appendNewLineIfNotEmpty() {
return this.append(NLEmpty);
}
/**
* Appends a soft {@link NewLineNode} to `this` node, if `condition` is equal to `true`.
* Soft {@link NewLineNode}s yield linebreaks in the derived generated text only if the preceding line is non-empty,
* i.e. there are non-whitespace characters added to the generated text since the last linebreak.
*
* @param condition a boolean value indicating whether to append a {@link NewLineNode} to `this`.
*
* @returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().append(
* entity.label ?? ''
* ).appendNewLineIfNotEmptyIf(entity.description !== undefined).append(
* entity.description
* )
*/
appendNewLineIfNotEmptyIf(condition) {
return condition ? this.appendNewLineIfNotEmpty() : this;
}
/**
* Convenience method for appending content in form of a template to `this` generation node.
*
* See {@link expandToNode} for details.
*
* @returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().appendTemplate
* `Hello World!`
* .appendNewLine()
*/
appendTemplate(staticParts, ...substitutions) {
return this.append(expandToNode(staticParts, ...substitutions));
}
/**
* Convenience method for appending content in form of a template to `this` generator node, if `condition` is equal to `true`.
*
* This method returns a tag function that takes the desired template and does the processing.
*
* If `condition` is satisfied the tagged template delegates to {@link appendTemplate}, otherwise it returns just `this`.
*
* See {@link expandToNode} for details.
*
* @param condition a boolean value indicating whether to append the template content to `this`.
*
* @returns a tag function behaving as described above, which in turn returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().appendTemplate
* `Hello World!`
* .appendNewLine().appendTemplateIf(entity !== undefined)
* `Hello ${entity?.name}!`
* .appendNewLineIfNotEmpty()
*/
appendTemplateIf(condition) {
return condition ? (staticParts, ...substitutions) => this.appendTemplate(staticParts, ...substitutions) : () => this;
}
/**
* Adds an area of indented text output.
* The content to be indented can be provided as an array consisting of strings and/or generation nodes
* (undefined is permitted), or via a callback offering the `indentingNode` to which the content shall be appended.
* Alternatively, an object satisfying {@link IndentConfig} can be provided taking the children as Array or via
* a callback as described previously via the `indentedChildren` property.
*
* The remaining properties of {@link IndentConfig} have the following effects:
* - `indentation`: a specific indentation length or string, defaults to the global indentation setting if omitted, see {@link toString},
* - `indentEmptyLines`: apply indentation to empty lines, defaults to `false`
* - `indentImmediately`: apply the indentation immediately starting at the first line, defaults to `true`, might be set to `false`
* if preceding content is not terminated by any `newline`. If `false` the indentation is inserted only after child `newline` nodes
* followed by further content.
*
* @param childrenOrConfig an {@link Array} or callback contributing the children, or a config object satisfying {@link IndentConfig} alternatively.
*
* @returns `this` {@link CompositeGeneratorNode} for convenience.
*
* @example
* new CompositeGeneratorNode().append(
* '{'
* ).indent(indentingNode =>
* indentingNode.append(
* 'name:', name, ','
* ).appendNewLine().appendIf(description !== undefined,
* 'description:', description
* ).appendNewLineIfNotEmpty()
* ).append(
* '}'
* );
*/
indent(childrenOrConfig) {
const { indentedChildren, indentation, indentEmptyLines, indentImmediately } = Array.isArray(childrenOrConfig) || typeof childrenOrConfig === 'function'
? { indentedChildren: childrenOrConfig }
: typeof childrenOrConfig === 'object' ? childrenOrConfig : {};
const node = new IndentNode(indentation, indentImmediately, indentEmptyLines);
this.contents.push(node);
if (Array.isArray(indentedChildren)) {
node.append(...indentedChildren);
}
else if (indentedChildren) {
node.append(indentedChildren);
}
return this;
}
// implementation:
appendTraced(source, property, index) {
return content => {
return this.append(new CompositeGeneratorNode().trace(source, property, index).append(content));
};
}
// implementation:
appendTracedIf(condition, source, property, index) {
return condition ? this.appendTraced((typeof source === 'function' ? source() : source), property, index) : () => this;
}
// implementation:
appendTracedTemplate(source, property, index) {
return (staticParts, ...substitutions) => {
return this.append(expandTracedToNode(source, property, index)(staticParts, ...substitutions));
};
}
// implementation:
appendTracedTemplateIf(condition, source, property, index) {
return condition ? this.appendTracedTemplate((typeof source === 'function' ? source() : source), property, index) : () => this;
}
}
// implementation
export function traceToNode(astNode, property, index) {
return content => {
if (content instanceof CompositeGeneratorNode && content.tracedSource === undefined) {
return content.trace(astNode, property, index);
}
else {
// a `content !== undefined` check is skipped here on purpose in order to let this method always return a result;
// dropping empty generator nodes is considered a post processing optimization.
return new CompositeGeneratorNode().trace(astNode, property, index).append(content);
}
};
}
// implementation
export function traceToNodeIf(condition, source, property, index) {
return condition ? traceToNode((typeof source === 'function' ? source() : source), property, index) : () => undefined;
}
/**
* Implementation of @{link GeneratorNode} denoting areas within the desired generated text of common increased indentation.
*/
export class IndentNode extends CompositeGeneratorNode {
constructor(indentation, indentImmediately = true, indentEmptyLines = false) {
super();
if (typeof (indentation) === 'string') {
this.indentation = indentation;
}
else if (typeof (indentation) === 'number') {
this.indentation = ''.padStart(indentation);
}
this.indentImmediately = indentImmediately;
this.indentEmptyLines = indentEmptyLines;
}
}
/**
* Implementation of @{link GeneratorNode} denoting linebreaks in the desired generated text.
*/
export class NewLineNode {
constructor(lineDelimiter = EOL, ifNotEmpty = false, count = 1) {
this.lineDelimiter = lineDelimiter;
this.ifNotEmpty = ifNotEmpty;
this.count = count;
}
}
export const NL = new NewLineNode();
export const NLEmpty = new NewLineNode(undefined, true);
//# sourceMappingURL=generator-node.js.map
File diff suppressed because one or more lines are too long
+40
View File
@@ -0,0 +1,40 @@
/******************************************************************************
* Copyright 2023 TypeFox GmbH
* This program and the accompanying materials are made available under the
* terms of the MIT License, which is available in the project root.
******************************************************************************/
import type { Range } from 'vscode-languageserver-textdocument';
import type { AstNode, CstNode } from '../syntax-tree.js';
import type { DocumentSegment } from '../workspace/documents.js';
export interface TraceSourceSpec {
astNode: AstNode;
property?: string;
index?: number;
}
export type SourceRegion = TextRegion | TextRegion2 | DocumentSegmentWithFileURI;
export interface TextRegion {
fileURI?: string;
offset: number;
end: number;
length?: number;
range?: Range;
}
interface TextRegion2 {
fileURI?: string;
offset: number;
length: number;
end?: number;
range?: Range;
}
export interface TraceRegion {
sourceRegion?: TextRegion;
targetRegion: TextRegion;
children?: TraceRegion[];
}
interface DocumentSegmentWithFileURI extends Omit<DocumentSegment, 'range'> {
fileURI?: string;
range?: Range;
}
export declare function getSourceRegion(sourceSpec: TraceSourceSpec | undefined | SourceRegion | SourceRegion[]): DocumentSegmentWithFileURI | CstNode | undefined;
export {};
//# sourceMappingURL=generator-tracing.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"generator-tracing.d.ts","sourceRoot":"","sources":["../../src/generate/generator-tracing.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAEhF,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,oCAAoC,CAAC;AAEhE,OAAO,KAAK,EAAE,OAAO,EAAE,OAAO,EAAkB,MAAM,mBAAmB,CAAC;AAI1E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAEjE,MAAM,WAAW,eAAe;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,MAAM,YAAY,GAAG,UAAU,GAAG,WAAW,GAAG,0BAA0B,CAAA;AAEhF,MAAM,WAAW,UAAU;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,KAAK,CAAC;CACjB;AAED,UAAU,WAAW;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,KAAK,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IACxB,YAAY,CAAC,EAAE,UAAU,CAAC;IAC1B,YAAY,EAAE,UAAU,CAAC;IACzB,QAAQ,CAAC,EAAE,WAAW,EAAE,CAAC;CAC5B;AAED,UAAU,0BAA2B,SAAQ,IAAI,CAAC,eAAe,EAAE,OAAO,CAAC;IACvE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,KAAK,CAAC;CACjB;AAKD,wBAAgB,eAAe,CAAC,UAAU,EAAE,eAAe,GAAG,SAAS,GAAG,YAAY,GAAG,YAAY,EAAE,GAAG,0BAA0B,GAAG,OAAO,GAAG,SAAS,CA0BzJ"}
+135
View File
@@ -0,0 +1,135 @@
/******************************************************************************
* Copyright 2023 TypeFox GmbH
* This program and the accompanying materials are made available under the
* terms of the MIT License, which is available in the project root.
******************************************************************************/
import { getDocument } from '../utils/ast-utils.js';
import { findNodesForProperty } from '../utils/grammar-utils.js';
import { TreeStreamImpl } from '../utils/stream.js';
export function getSourceRegion(sourceSpec) {
if (!sourceSpec) {
return undefined;
}
else if ('astNode' in sourceSpec) {
return getSourceRegionOfAstNode(sourceSpec);
}
else if (Array.isArray(sourceSpec)) {
return sourceSpec.reduce(mergeDocumentSegment, undefined); // apply mergeDocumentSegment for single entry sourcSpec lists, too, thus start with 'undefined' as initial value
}
else {
// some special treatment of cstNodes for revealing the uri of the defining DSL text file
// is currently only done for single cstNode tracings, like "expandTracedToNode(source.$cstNode)`...`",
// is _not done_ for multi node tracings like below, see if case above
// joinTracedToNode( [
// findNodeForKeyword(source.$cstNode, '{')!,
// findNodeForKeyword(source.$cstNode, '}')!
// ] )(source.children, c => c.name)
const sourceRegion = sourceSpec;
const sourceFileURIviaCstNode = isCstNode(sourceRegion)
? getDocumentURIOrUndefined(sourceRegion?.root?.astNode ?? sourceRegion?.astNode) : undefined;
return copyDocumentSegment(sourceRegion, sourceFileURIviaCstNode);
}
}
function isCstNode(segment) {
return typeof segment !== 'undefined' && 'element' in segment && 'text' in segment;
}
function getDocumentURIOrUndefined(astNode) {
try {
return getDocument(astNode).uri.toString();
}
catch (_error) {
return undefined;
}
}
function getSourceRegionOfAstNode(sourceSpec) {
const { astNode, property, index } = sourceSpec ?? {};
const textRegion = astNode?.$cstNode ?? astNode?.$textRegion;
if (astNode === undefined || textRegion === undefined) {
return undefined;
}
else if (property === undefined) {
return copyDocumentSegment(textRegion, getDocumentURI(astNode));
}
else {
const getSingleOrCompoundRegion = (regions) => {
if (index !== undefined && index > -1 && Array.isArray(astNode[property])) {
return index < regions.length ? regions[index] : undefined;
}
else {
return regions.reduce(mergeDocumentSegment, undefined);
}
};
if (textRegion.assignments?.[property]) {
const region = getSingleOrCompoundRegion(textRegion.assignments[property]);
return region && copyDocumentSegment(region, getDocumentURI(astNode));
}
else if (astNode.$cstNode) {
const region = getSingleOrCompoundRegion(findNodesForProperty(astNode.$cstNode, property));
return region && copyDocumentSegment(region, getDocumentURI(astNode));
}
else {
return undefined;
}
}
}
function getDocumentURI(astNode) {
if (astNode.$cstNode) {
return getDocument(astNode)?.uri?.toString();
}
else if (astNode.$textRegion) {
return astNode.$textRegion.documentURI
|| new TreeStreamImpl(astNode, n => n.$container ? [n.$container] : []).find(n => n.$textRegion?.documentURI)?.$textRegion?.documentURI;
}
else {
return undefined;
}
}
function copyDocumentSegment(region, fileURI) {
const result = {
offset: region.offset,
end: region.end ?? region.offset + region.length,
length: region.length ?? region.end - region.offset,
};
if (region.range) {
result.range = region.range;
}
fileURI ?? (fileURI = region.fileURI);
if (fileURI) {
result.fileURI = fileURI;
}
return result;
}
function mergeDocumentSegment(prev, curr) {
if (!prev) {
return curr && copyDocumentSegment(curr);
}
else if (!curr) {
return prev && copyDocumentSegment(prev);
}
const prevEnd = prev.end ?? prev.offset + prev.length;
const currEnd = curr.end ?? curr.offset + curr.length;
const offset = Math.min(prev.offset, curr.offset);
const end = Math.max(prevEnd, currEnd);
const length = end - offset;
const result = {
offset, end, length,
};
if (prev.range && curr.range) {
result.range = {
start: curr.range.start.line < prev.range.start.line
|| curr.range.start.line === prev.range.start.line && curr.range.start.character < prev.range.start.character
? curr.range.start : prev.range.start,
end: curr.range.end.line > prev.range.end.line
|| curr.range.end.line === prev.range.end.line && curr.range.end.character > prev.range.end.character
? curr.range.end : prev.range.end
};
}
if (prev.fileURI || curr.fileURI) {
const prevURI = prev.fileURI;
const currURI = curr.fileURI;
const fileURI = prevURI && currURI && prevURI !== currURI ? `<unmergable text regions of ${prevURI}, ${currURI}>` : prevURI ?? currURI;
result.fileURI = fileURI;
}
return result;
}
//# sourceMappingURL=generator-tracing.js.map
File diff suppressed because one or more lines are too long
+13
View File
@@ -0,0 +1,13 @@
/******************************************************************************
* Copyright 2023 TypeFox GmbH
* This program and the accompanying materials are made available under the
* terms of the MIT License, which is available in the project root.
*
* @module langium/generate
*/
export * from './generator-node.js';
export type { SourceRegion, TextRegion, TraceRegion, TraceSourceSpec } from './generator-tracing.js';
export * from './node-joiner.js';
export * from './template-node.js';
export { expandToString, expandToStringLF, expandToStringLFWithNL, expandToStringWithNL, normalizeEOL } from './template-string.js';
//# sourceMappingURL=index.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/generate/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,cAAc,qBAAqB,CAAC;AACpC,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACrG,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC"}
+12
View File
@@ -0,0 +1,12 @@
/******************************************************************************
* Copyright 2023 TypeFox GmbH
* This program and the accompanying materials are made available under the
* terms of the MIT License, which is available in the project root.
*
* @module langium/generate
*/
export * from './generator-node.js';
export * from './node-joiner.js';
export * from './template-node.js';
export { expandToString, expandToStringLF, expandToStringLFWithNL, expandToStringWithNL, normalizeEOL } from './template-string.js';
//# sourceMappingURL=index.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/generate/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,cAAc,qBAAqB,CAAC;AAEpC,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC"}
+308
View File
@@ -0,0 +1,308 @@
/******************************************************************************
* Copyright 2023 TypeFox GmbH
* This program and the accompanying materials are made available under the
* terms of the MIT License, which is available in the project root.
******************************************************************************/
import type { AstNode, Properties } from '../syntax-tree.js';
import { CompositeGeneratorNode, type Generated, NewLineNode } from './generator-node.js';
import type { SourceRegion } from './generator-tracing.js';
export interface JoinOptions<T, U extends T = T> {
/**
* A plain or type guard filter function.
*
* Benefit compared to pre-filtering the joined iterable: Original indices of the elements are preserved and forwarded to the `toGenerated` function.
*/
filter?: ((element: T, index: number, isLast: boolean) => boolean) | ((element: T, index: number, isLast: boolean) => element is U);
/** A fixed prefix or prefix computation function to be prepended before each element of the iterable. */
prefix?: Generated | ((element: U, index: number, isLast: boolean) => Generated | undefined);
/** A fixed suffix or suffix computation function to be appended after each element of the iterable. */
suffix?: Generated | ((element: U, index: number, isLast: boolean) => Generated | undefined);
/** A fixed element separator to be inserted between 2 consecutive non-undefined item representations incl. their suffixes and prefixes. */
separator?: Generated;
/**
* Activates appending of up 6 line breaks after a non-undefined element + suffix + separator if given.
*
* If `true` a single line break is appended.
*
* If a number `> 6` is required you can achieve that via the `separator` or `suffix` options,
* e.g. `separator: new CompositeGeneratorNode(calcLineBreaks(...))`.
*/
appendNewLineIfNotEmpty?: true | NewLineNode.NoOfLineBreaks;
/**
* Suppresses appending trailing line breaks after the last item in the iterable if activated via `appendNewLineIfNotEmpty`.
*/
skipNewLineAfterLastItem?: true;
}
/**
* Joins the elements of the given `iterable` of pre-computed instances of {@link Generated}
* by appending the results to a {@link CompositeGeneratorNode} being returned finally.
* Each individual element is tested to be a string, a {@link CompositeGeneratorNode},
* or `undefined` and included as is if that test is satisfied. Otherwise the result of
* applying {@link String} (string constructor) to the element is included.
*
* Note: empty strings being included in `iterable` are treated as ordinary string
* representations, while the value of `undefined` makes this function to ignore the
* corresponding item and no separator is appended, if configured.
*
* Examples:
* ```
* expandToNode`
* ${ joinToNode(['a', 'b'], { appendNewLineIfNotEmpty: true }) }
*
* ${ joinToNode(new Set(['a', undefined, getElementNode()]), { separator: ',', appendNewLineIfNotEmpty: true }) }
* `
* ```
*
* @param iterable an {@link Array} or {@link Iterable} providing the elements to be joined
*
* @param options optional config object for defining a `separator`, contributing specialized
* `prefix` and/or `suffix` providers, and activating conditional line-break insertion. In addition,
* a dedicated `filter` function can be provided that enables the provision of the original
* element indices to the aforementioned functions, if the list is to be filtered. If
* {@link Array.filter} would be applied to the original list, the indices will be those of the
* filtered list during subsequent processing that in particular will cause confusion when using
* the tracing variant of this function named ({@link joinTracedToNode}).
* @returns the resulting {@link CompositeGeneratorNode} representing `iterable`'s content
*/
export declare function joinToNode<Generated>(iterable: Iterable<Generated> | Generated[], options?: JoinOptions<Generated>): CompositeGeneratorNode | undefined;
/**
* Joins the elements of the given `iterable` by applying `toGenerated` to each element
* and appending the results to a {@link CompositeGeneratorNode} being returned finally.
*
* Note: empty strings being returned by `toGenerated` are treated as ordinary string
* representations, while the result of `undefined` makes this function to ignore the
* corresponding item and no separator is appended, if configured.
*
* Examples:
* ```
* expandToNode`
* ${ joinToNode(['a', 'b'], String, { appendNewLineIfNotEmpty: true }) }
*
* ${ joinToNode(new Set(['a', undefined, 'b']), e => e && String(e), { separator: ',', appendNewLineIfNotEmpty: true }) }
* `
* ```
*
* @param iterable an {@link Array} or {@link Iterable} providing the elements to be joined
*
* @param toGenerated a callback converting each individual element to a string, a
* {@link CompositeGeneratorNode}, or to `undefined` if to be omitted, defaults to the `identity`
* for strings, generator nodes, and `undefined`, and to {@link String} otherwise.
*
* @param options optional config object for defining a `separator`, contributing specialized
* `prefix` and/or `suffix` providers, and activating conditional line-break insertion. In addition,
* a dedicated `filter` function can be provided that enables the provision of the
* original element indices to the aforementioned functions, if the list is to be filtered. If
* {@link Array.filter} would be applied to the original list, the indices will be those of the
* filtered list during subsequent processing that in particular will cause confusion when using
* the tracing variant of this function named ({@link joinTracedToNode}).
* @returns the resulting {@link CompositeGeneratorNode} representing `iterable`'s content
*/
export declare function joinToNode<T>(iterable: Iterable<T> | T[], toGenerated?: ((element: T, index: number, isLast: boolean) => Generated), options?: JoinOptions<T>): CompositeGeneratorNode | undefined;
/**
* Joins the elements of the given `iterable` by applying `toGenerated` to each element
* and appending the results to a {@link CompositeGeneratorNode} being returned finally.
*
* Here the mandatory type guard `filter` function is used to filter the elements of
* `iterable` and to apply the `toGenerated` function as well as the optional
* `prefix` and `suffix` functions to the accepted items with the more specific type
* `U` and their original indices.
*
* Note: empty strings being returned by `toGenerated` are treated as ordinary string
* representations, while the result of `undefined` makes this function to ignore the
* corresponding item and no separator is appended, if configured.
*
* Example:
* ```
* expandToNode`
* ${ joinToNode([x, y], e => e.propertyOfX, { filter: (e): e is X => e.$type === 'X' }) }
* `
* ```
*
* @param iterable an {@link Array} or {@link Iterable} providing the elements to be joined
*
* @param toGenerated a callback converting each individual element to a string, a
* {@link CompositeGeneratorNode}, or to `undefined` if to be omitted, defaults to the `identity`
* for strings, generator nodes, and `undefined`, and to {@link String} otherwise.
*
* @param options config object including the here required type guard filter function, as well as
* optional `separator` and `prefix` and/or `suffix` providers, and activating conditional
* line-break insertion.
* In contrast to {@link Array.filter} the dedicated `filter` function enables the provision of the
* original element indices to `toGenerated` and the aforementioned functions, if the list is to be
* filtered.
* @returns the resulting {@link CompositeGeneratorNode} representing `iterable`'s content
*/
export declare function joinToNode<T, U extends T>(iterable: Iterable<T> | T[], toGenerated: ((element: U, index: number, isLast: boolean) => Generated), options: JoinOptions<T, U> & {
filter: (element: T, index: number, isLast: boolean) => element is U;
}): CompositeGeneratorNode | undefined;
/**
* Convenience function for joining the elements of some `iterable` and gathering tracing information.
*
* This function returns another function that does the processing, and that expects same list of
* arguments as expected by {@link joinToNode}, i.e. an `iterable`, a function `toGenerated`
* converting each element into a `Generated`, as well as some `options`.
*
* That function then joins the elements of `iterable` by delegating to {@link joinToNode}.
* Via {@link traceToNode} the resulting generator node is supplemented with the provided tracing
* information in form of `{astNode, property?, index?}`, and finally returned. In addition,
* if `property` is given each element's generator node representation is augmented with the
* provided tracing information plus the index of the element within `iterable`.
*
* @param astNode the AstNode corresponding to the appended content
*
* @param property the value property name (string) corresponding to the appended content,
* if e.g. the content corresponds to some `string` or `number` property of `astNode`, is optional
*
* @returns a function behaving as described above, which in turn returns a {@link CompositeGeneratorNode}.
*
* @example
* expandToNode`
* children: ${ joinTracedToNode(entity, 'children')(entity.children, child => child.name, { separator: ' ' }) };
* `.appendNewLine()
*/
export declare function joinTracedToNode<T extends AstNode>(astNode: T, property?: Properties<T>): <E>(iterable: Iterable<E> | E[], toGenerated?: ((element: E, index: number, isLast: boolean) => Generated) | JoinOptions<E>, options?: JoinOptions<E>) => CompositeGeneratorNode;
/**
* Convenience function for joining the elements of some `iterable` and gathering tracing information
* in form of concrete coordinates.
*
* This function returns another function that does the processing, and that expects same list of
* arguments as expected by {@link joinToNode}, i.e. an `iterable`, a function `toGenerated`
* converting each element into a `Generated`, as well as some `options`.
*
* That function then joins the elements of `iterable` by delegating to {@link joinToNode}.
* Via {@link traceToNode} the resulting generator node is supplemented with the provided tracing
* information, and finally returned. Elementwise tracing need to be implemented by client code
* within `toGenerated`, if required.
*
* @param sourceRegion a text region within some file in form of concrete coordinates,
* if `undefined` no tracing will happen
*
* @returns a function behaving as described above, which in turn returns a {@link CompositeGeneratorNode}.
*
* @example
* expandToNode`
* children: ${ joinTracedToNode(findNodesForProperty(entity.$cstNode, 'children'))(entity.children, child => child.name, { separator: ' ' }) };
* `.appendNewLine()
*/
export declare function joinTracedToNode(sourceRegion: SourceRegion | undefined): <E>(iterable: Iterable<E> | E[], toGenerated?: ((element: E, index: number, isLast: boolean) => Generated) | JoinOptions<E>, options?: JoinOptions<E>) => CompositeGeneratorNode;
/**
* Convenience function for joining the elements of some `iterable` and gathering tracing information
* in form of a list of concrete coordinates.
*
* This function returns another function that does the processing, and that expects same list of
* arguments as expected by {@link joinToNode}, i.e. an `iterable`, a function `toGenerated`
* converting each element into a `Generated`, as well as some `options`.
*
* That function then joins the elements of `iterable` by delegating to {@link joinToNode}.
* Via {@link traceToNode} the resulting generator node is supplemented with the provided tracing
* information, and finally returned. Elementwise tracing need to be implemented by client code
* within `toGenerated`, if required.
*
* The list of regions in `sourceRegions` will later be reduced to the smallest encompassing region
* of all the contained source regions.
*
* @param sourceRegions a list of text regions within some file in form of concrete coordinates,
* if empty no tracing will happen
*
* @returns a function behaving as described above, which in turn returns a {@link CompositeGeneratorNode}.
*
* @example
* expandToNode`
* children: ${ joinTracedToNode(findNodesForProperty(entity.$cstNode, 'children'))(entity.children, child => child.name, { separator: ' ' }) };
* `.appendNewLine()
*/
export declare function joinTracedToNode(sourceRegions: SourceRegion[]): <E>(iterable: Iterable<E> | E[], toGenerated?: ((element: E, index: number, isLast: boolean) => Generated) | JoinOptions<E>, options?: JoinOptions<E>) => CompositeGeneratorNode;
/**
* Convenience function for joining the elements of some `iterable` and gathering tracing information,
* if `condition` is equal to `true`.
*
* If `condition` is satisfied, this function returns another function that does the processing,
* and that expects same list of arguments as expected by {@link joinToNode}, i.e. an `iterable`,
* a function `toGenerated` converting each element into a `Generated`, as well as some `options`.
*
* That function then joins the elements of `iterable` by delegating to {@link joinToNode}.
* Via {@link traceToNode} the resulting generator node is supplemented with the provided tracing
* information, and finally returned. In addition, if `property` is given each element's
* generator node representation is augmented with the provided tracing information
* plus the index of the element within `iterable`.
*
* Otherwise, if `condition` is equal to false, the returned function just returns `undefined`.
*
* @param condition a boolean value indicating whether to evaluate the provided iterable.
*
* @param astNode the AstNode corresponding to the appended content
*
* @param property the value property name (string) corresponding to the appended content,
* if e.g. the content corresponds to some `string` or `number` property of `astNode`, is optional
*
* @returns a function behaving as described above, which in turn returns a {@link CompositeGeneratorNode} or `undefined`.
*
* @example
* expandToNode`
* children: ${ joinTracedToNode(entity, 'children')(entity.children, child => child.name, { separator: ' ' }) };
* `.appendNewLine()
*/
export declare function joinTracedToNodeIf<T extends AstNode>(condition: boolean, astNode: T, property?: Properties<T>): <E>(iterable: Iterable<E> | E[], toGenerated?: ((element: E, index: number, isLast: boolean) => Generated) | JoinOptions<E>, options?: JoinOptions<E>) => CompositeGeneratorNode | undefined;
/**
* Convenience function for joining the elements of some `iterable` and gathering tracing information
* in form of a list of concrete coordinates, if `condition` is equal to `true`.
*
* If `condition` is satisfied, this function returns another function that does the processing,
* and that expects same list of arguments as expected by {@link joinToNode}, i.e. an `iterable`,
* a function `toGenerated` converting each element into a `Generated`, as well as some `options`.
*
* That function then joins the elements of `iterable` by delegating to {@link joinToNode}.
* Via {@link traceToNode} the resulting generator node is supplemented with the provided tracing
* information, and finally returned. Element-wise tracing need to be implemented by client code
* within `toGenerated`, if required.
*
* Otherwise, if `condition` is equal to false, the returned function just returns `undefined`.
*
* If `sourceRegion` is a function supplying the corresponding regions, it's only called if `condition` is satisfied.
*
* @param condition a boolean value indicating whether to evaluate the provided iterable.
*
* @param sourceRegion a text region within some file in form of concrete coordinates or a supplier function,
* if `undefined` no tracing will happen
*
* @returns a function behaving as described above, which in turn returns a {@link CompositeGeneratorNode}.
*
* @example
* expandToNode`
* children: ${ joinTracedToNodeIf(entity !== undefined, () => entity.$cstNode)(entity.children, child => child.name, { separator: ' ' }) };
* `.appendNewLine()
*/
export declare function joinTracedToNodeIf(condition: boolean, sourceRegion: SourceRegion | undefined | (() => SourceRegion | undefined)): <E>(iterable: Iterable<E> | E[], toGenerated?: ((element: E, index: number, isLast: boolean) => Generated) | JoinOptions<E>, options?: JoinOptions<E>) => CompositeGeneratorNode | undefined;
/**
* Convenience function for joining the elements of some `iterable` and gathering tracing information
* in form of a list of concrete coordinates, if `condition` is equal to `true`.
*
* If `condition` is satisfied, this function returns another function that does the processing,
* and that expects same list of arguments as expected by {@link joinToNode}, i.e. an `iterable`,
* a function `toGenerated` converting each element into a `Generated`, as well as some `options`.
*
* That function then joins the elements of `iterable` by delegating to {@link joinToNode}.
* Via {@link traceToNode} the resulting generator node is supplemented with the provided tracing
* information, and finally returned. Element-wise tracing need to be implemented by client code
* within `toGenerated`, if required.
*
* Otherwise, if `condition` is equal to false, the returned function just returns `undefined`.
*
* The list of regions in `sourceRegions` will later be reduced to the smallest encompassing region
* of all the contained source regions.
* If `sourceRegions` is a function supplying the corresponding regions, it's only called if `condition` is satisfied.
*
* @param condition a boolean value indicating whether to evaluate the provided iterable.
*
* @param sourceRegions a list of text regions within some file in form of concrete coordinates or a supplier function,
* if empty no tracing will happen
*
* @returns a function behaving as described above, which in turn returns a {@link CompositeGeneratorNode}.
*
* @example
* expandToNode`
* children: ${ joinTracedToNodeIf(entity !== undefined, () => findNodesForProperty(entity.$cstNode, 'children'))(entity.children, child => child.name, { separator: ' ' }) };
* `.appendNewLine()
*/
export declare function joinTracedToNodeIf(condition: boolean, sourceRegions: SourceRegion[] | (() => SourceRegion[])): <E>(iterable: Iterable<E> | E[], toGenerated?: ((element: E, index: number, isLast: boolean) => Generated) | JoinOptions<E>, options?: JoinOptions<E>) => CompositeGeneratorNode | undefined;
//# sourceMappingURL=node-joiner.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"node-joiner.d.ts","sourceRoot":"","sources":["../../src/generate/node-joiner.ts"],"names":[],"mappings":"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"}
+67
View File
@@ -0,0 +1,67 @@
/******************************************************************************
* Copyright 2023 TypeFox GmbH
* This program and the accompanying materials are made available under the
* terms of the MIT License, which is available in the project root.
******************************************************************************/
import { CompositeGeneratorNode, isGeneratorNode, NewLineNode, traceToNode } from './generator-node.js';
const defaultToGenerated = (e) => e === undefined || typeof e === 'string' || isGeneratorNode(e) ? e : String(e);
export function joinToNode(iterable, toGeneratedOrOptions = defaultToGenerated, options = {}) {
const toGenerated = typeof toGeneratedOrOptions === 'function' ? toGeneratedOrOptions : defaultToGenerated;
const { filter, prefix, suffix, separator, appendNewLineIfNotEmpty, skipNewLineAfterLastItem } = typeof toGeneratedOrOptions === 'object' ? toGeneratedOrOptions : options;
const prefixFunc = typeof prefix === 'function' ? prefix : (() => prefix);
const suffixFunc = typeof suffix === 'function' ? suffix : (() => suffix);
const doAppendNewLines = (node) => {
if (node.isEmpty()) {
// do nothing
return;
}
else if (appendNewLineIfNotEmpty === true || appendNewLineIfNotEmpty === 1) {
node.appendNewLineIfNotEmpty();
}
else {
node.append(new NewLineNode(undefined, true, appendNewLineIfNotEmpty));
}
};
return reduceWithIsLast(iterable, (node, it, i, isLast) => {
if (filter && !filter(it, i, isLast)) {
return node;
}
const content = toGenerated(it, i, isLast);
return content === undefined ? /* in this case don't append anything to */ node : /* otherwise: */ (node ?? (node = new CompositeGeneratorNode()))
.appendIf(!node.isEmpty(), separator)
.appendIf(
// append 'newLineIfNotEmpty' elements only if 'node' has some content already,
// as if the parent is an IndentNode with 'indentImmediately' set to 'false'
// the indentation is not properly applied to the first non-empty line of the (this) child node
appendNewLineIfNotEmpty !== undefined, doAppendNewLines)
.append(prefixFunc(it, i, isLast))
.append(content)
.append(suffixFunc(it, i, isLast));
})?.appendIf(!skipNewLineAfterLastItem && appendNewLineIfNotEmpty !== undefined, doAppendNewLines);
}
// implementation:
export function joinTracedToNode(source, property) {
return (iterable, toGeneratedOrOptions, options) => {
options ?? (options = typeof toGeneratedOrOptions === 'object' ? toGeneratedOrOptions : undefined);
const toGenerated = typeof toGeneratedOrOptions === 'function' ? toGeneratedOrOptions : defaultToGenerated;
return traceToNode(source, property)(joinToNode(iterable, source && property ? (element, index, isLast) => traceToNode(source, property, index)(toGenerated(element, index, isLast)) : toGenerated, options));
};
}
// implementation:
export function joinTracedToNodeIf(condition, source, property) {
return condition ? joinTracedToNode((typeof source === 'function' ? source() : source), property) : () => undefined;
}
function reduceWithIsLast(iterable, callbackfn, initial) {
const iterator = iterable[Symbol.iterator]();
let next = iterator.next();
let index = 0;
let result = initial;
while (!next.done) {
const nextNext = iterator.next();
result = callbackfn(result, next.value, index, Boolean(nextNext.done));
next = nextNext;
index++;
}
return result;
}
//# sourceMappingURL=node-joiner.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"node-joiner.js","sourceRoot":"","sources":["../../src/generate/node-joiner.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAGhF,OAAO,EAAE,sBAAsB,EAAkB,eAAe,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAoCxH,MAAM,kBAAkB,GAAG,CAAC,CAAU,EAAa,EAAE,CAAC,CAAC,KAAK,SAAS,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;AAoHrI,MAAM,UAAU,UAAU,CACtB,QAA2B,EAC3B,uBAAqG,kBAAkB,EACvH,UAA0B,EAAE;IAG5B,MAAM,WAAW,GAAG,OAAO,oBAAoB,KAAK,UAAU,CAAC,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC,kBAAkB,CAAC;IAC3G,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,uBAAuB,EAAE,wBAAwB,EAAE,GAAG,OAAO,oBAAoB,KAAK,QAAQ,CAAC,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC,OAAO,CAAC;IAE3K,MAAM,UAAU,GAAG,OAAO,MAAM,KAAK,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,CAAC;IAC1E,MAAM,UAAU,GAAG,OAAO,MAAM,KAAK,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,CAAC;IAE1E,MAAM,gBAAgB,GAAG,CAAC,IAA4B,EAAE,EAAE;QACtD,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,CAAC;YACjB,aAAa;YACb,OAAO;QACX,CAAC;aAAM,IAAI,uBAAuB,KAAK,IAAI,IAAI,uBAAuB,KAAK,CAAC,EAAE,CAAC;YAC3E,IAAI,CAAC,uBAAuB,EAAE,CAAC;QACnC,CAAC;aAAM,CAAC;YACJ,IAAI,CAAC,MAAM,CAAC,IAAI,WAAW,CAAC,SAAS,EAAE,IAAI,EAAE,uBAAuB,CAAC,CAAC,CAAC;QAC3E,CAAC;IACL,CAAC,CAAC;IAEF,OAAO,gBAAgB,CAA4B,QAAQ,EAAE,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE;QACjF,IAAI,MAAM,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC,EAAE,MAAM,CAAC,EAAE,CAAC;YACnC,OAAO,IAAI,CAAC;QAChB,CAAC;QACD,MAAM,OAAO,GAAG,WAAW,CAAC,EAAE,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC;QAC3C,OAAO,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,2CAA2C,CAAC,IAAI,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,IAAI,KAAJ,IAAI,GAAK,IAAI,sBAAsB,EAAE,EAAC;aACrI,QAAQ,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,CAAC;aACpC,QAAQ;QACL,+EAA+E;QAC/E,6EAA6E;QAC7E,gGAAgG;QAChG,uBAAuB,KAAK,SAAS,EACrC,gBAAgB,CACnB;aACA,MAAM,CAAC,UAAU,CAAC,EAAE,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC;aACjC,MAAM,CAAC,OAAO,CAAC;aACf,MAAM,CAAC,UAAU,CAAC,EAAE,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;IAC3C,CAAC,CAAC,EAAE,QAAQ,CACR,CAAC,wBAAwB,IAAI,uBAAuB,KAAK,SAAS,EAClE,gBAAgB,CACnB,CAAC;AACN,CAAC;AAqFD,kBAAkB;AAClB,MAAM,UAAU,gBAAgB,CAAoB,MAAqD,EAAE,QAAwB;IAE/H,OAAO,CAAC,QAAQ,EAAE,oBAAoB,EAAE,OAAO,EAAE,EAAE;QAC/C,OAAO,KAAP,OAAO,GAAK,OAAO,oBAAoB,KAAK,QAAQ,CAAC,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC,SAAS,EAAC;QACxF,MAAM,WAAW,GAAG,OAAO,oBAAoB,KAAK,UAAU,CAAC,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC,kBAAkB,CAAC;QAC3G,OAAO,WAAW,CAAC,MAAW,EAAE,QAAQ,CAAC,CACrC,UAAU,CACN,QAAQ,EACR,MAAM,IAAI,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,CAAC,WAAW,CAAC,MAAW,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC,WAAW,CAAC,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,EAC7I,OAAO,CACV,CACJ,CAAC;IACN,CAAC,CAAC;AACN,CAAC;AAqGD,kBAAkB;AAClB,MAAM,UAAU,kBAAkB,CAAoB,SAAkB,EAAE,MAAyG,EAAE,QAAwB;IAEzM,OAAO,SAAS,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,OAAO,MAAM,KAAK,UAAU,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,MAAM,CAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC;AAC7H,CAAC;AAED,SAAS,gBAAgB,CACrB,QAA2B,EAC3B,UAAyG,EACzG,OAAW;IAEX,MAAM,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;IAC7C,IAAI,IAAI,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC;IAC3B,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,MAAM,GAAG,OAAO,CAAC;IAErB,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;QAChB,MAAM,QAAQ,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC;QACjC,MAAM,GAAG,UAAU,CAAC,MAAM,EAAE,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;QACvE,IAAI,GAAG,QAAQ,CAAC;QAChB,KAAK,EAAE,CAAC;IACZ,CAAC;IAED,OAAO,MAAM,CAAC;AAClB,CAAC"}
+12
View File
@@ -0,0 +1,12 @@
/******************************************************************************
* Copyright 2021 TypeFox GmbH
* This program and the accompanying materials are made available under the
* terms of the MIT License, which is available in the project root.
******************************************************************************/
import type { GeneratorNode } from './generator-node.js';
import type { TraceRegion } from './generator-tracing.js';
export declare function processGeneratorNode(node: GeneratorNode, defaultIndentation?: string | number): {
text: string;
trace: TraceRegion;
};
//# sourceMappingURL=node-processor.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"node-processor.d.ts","sourceRoot":"","sources":["../../src/generate/node-processor.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAGhF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAA4B,WAAW,EAAE,MAAM,wBAAwB,CAAC;AA2KpF,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,aAAa,EAAE,kBAAkB,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,WAAW,CAAA;CAAE,CAwBpI"}
+258
View File
@@ -0,0 +1,258 @@
/******************************************************************************
* Copyright 2021 TypeFox GmbH
* This program and the accompanying materials are made available under the
* terms of the MIT License, which is available in the project root.
******************************************************************************/
import { CompositeGeneratorNode, IndentNode, NewLineNode } from './generator-node.js';
import { getSourceRegion } from './generator-tracing.js';
class Context {
constructor(defaultIndent) {
this.defaultIndentation = ' ';
this.pendingIndent = true;
this.currentIndents = [];
this.recentNonImmediateIndents = [];
this.traceData = [];
this.lines = [[]];
this.length = 0;
if (typeof defaultIndent === 'string') {
this.defaultIndentation = defaultIndent;
}
else if (typeof defaultIndent === 'number') {
this.defaultIndentation = ''.padStart(defaultIndent);
}
}
get content() {
return this.lines.map(e => e.join('')).join('');
}
get contentLength() {
return this.length;
}
get currentLineNumber() {
return this.lines.length - 1;
}
get currentLineContent() {
return this.lines[this.currentLineNumber].join('');
}
get currentPosition() {
return {
offset: this.contentLength,
line: this.currentLineNumber,
character: this.currentLineContent.length
};
}
append(value, isIndent) {
if (value.length > 0) {
const beforePos = isIndent && this.currentPosition;
this.lines[this.currentLineNumber].push(value);
this.length += value.length;
if (beforePos) {
this.indentPendingTraceRegions(beforePos);
}
}
}
indentPendingTraceRegions(before) {
for (let i = this.traceData.length - 1; i >= 0; i--) {
const tr = this.traceData[i];
if (tr.targetStart && tr.targetStart.offset === before.offset /* tr.targetStart.line == before.line && tr.targetStart.character === before.character*/)
tr.targetStart = this.currentPosition;
}
}
increaseIndent(node) {
this.currentIndents.push(node);
if (!node.indentImmediately) {
this.recentNonImmediateIndents.push(node);
}
}
decreaseIndent() {
this.currentIndents.pop();
}
get relevantIndents() {
return this.currentIndents.filter(i => !this.recentNonImmediateIndents.includes(i));
}
resetCurrentLine() {
this.length -= this.lines[this.currentLineNumber].join('').length;
this.lines[this.currentLineNumber] = [];
this.pendingIndent = true;
this.recentNonImmediateIndents.length = 0;
}
addNewLine() {
this.lines.push([]);
this.pendingIndent = true;
this.recentNonImmediateIndents.length = 0;
}
pushTraceRegion(sourceRegion) {
const region = createTraceRegion(sourceRegion, this.currentPosition, it => this.traceData[this.traceData.length - 1]?.children?.push(it));
this.traceData.push(region);
return region;
}
popTraceRegion(expected) {
const traceRegion = this.traceData.pop();
// the following assertion can be dropped once the tracing is considered stable
this.assertTrue(traceRegion === expected, 'Trace region mismatch!');
return traceRegion;
}
getParentTraceSourceFileURI() {
for (let i = this.traceData.length - 1; i > -1; i--) {
const fileUri = this.traceData[i].sourceRegion?.fileURI;
if (fileUri)
return fileUri;
}
return undefined;
}
assertTrue(condition, msg) {
if (!condition) {
throw new Error(msg);
}
}
}
function createTraceRegion(sourceRegion, targetStart, accept) {
const result = {
sourceRegion,
targetRegion: undefined,
children: [],
targetStart,
complete: (targetEnd) => {
result.targetRegion = {
offset: result.targetStart.offset,
end: targetEnd.offset,
length: targetEnd.offset - result.targetStart.offset,
range: {
start: {
line: result.targetStart.line,
character: result.targetStart.character
},
end: {
line: targetEnd.line,
character: targetEnd.character
},
}
};
delete result.targetStart;
if (result.children?.length === 0) {
delete result.children;
}
if (result.targetRegion?.length) {
accept(result);
}
delete result.complete;
return result;
}
};
return result;
}
export function processGeneratorNode(node, defaultIndentation) {
const context = new Context(defaultIndentation);
const trace = context.pushTraceRegion(undefined);
processNodeInternal(node, context);
context.popTraceRegion(trace);
trace.complete && trace.complete(context.currentPosition);
const singleChild = trace.children && trace.children.length === 1 ? trace.children[0] : undefined;
const singleChildTargetRegion = singleChild?.targetRegion;
const rootTargetRegion = trace.targetRegion;
if (singleChildTargetRegion && singleChild.sourceRegion
&& singleChildTargetRegion.offset === rootTargetRegion.offset
&& singleChildTargetRegion.length === rootTargetRegion.length) {
// some optimization:
// if (the root) `node` is traced (`singleChild.sourceRegion` !== undefined) and spans the entire `context.content`
// we skip the wrapping root trace object created above at the beginning of this method
return { text: context.content, trace: singleChild };
}
else {
return { text: context.content, trace };
}
}
function processNodeInternal(node, context) {
if (typeof (node) === 'string') {
processStringNode(node, context);
}
else if (node instanceof IndentNode) {
processIndentNode(node, context);
}
else if (node instanceof CompositeGeneratorNode) {
processCompositeNode(node, context);
}
else if (node instanceof NewLineNode) {
processNewLineNode(node, context);
}
}
function hasContent(node, ctx) {
if (typeof (node) === 'string') {
return node.length !== 0; // cs: do not ignore ws only content here, enclosed within other nodes it will matter!
}
else if (node instanceof CompositeGeneratorNode) {
return node.contents.some(e => hasContent(e, ctx));
}
else if (node instanceof NewLineNode) {
return !(node.ifNotEmpty && ctx.currentLineContent.length === 0);
}
else {
return false;
}
}
function processStringNode(node, context) {
if (node) {
handlePendingIndent(context, false);
context.append(node);
}
}
function handlePendingIndent(ctx, endOfLine) {
if (ctx.pendingIndent) {
let indent = '';
for (const indentNode of ctx.relevantIndents.filter(e => e.indentEmptyLines || !endOfLine)) {
indent += indentNode.indentation ?? ctx.defaultIndentation;
}
ctx.append(indent, true);
ctx.pendingIndent = false;
}
}
function processCompositeNode(node, context) {
let traceRegion = undefined;
const sourceRegion = getSourceRegion(node.tracedSource);
if (sourceRegion) {
traceRegion = context.pushTraceRegion(sourceRegion);
}
for (const child of node.contents) {
processNodeInternal(child, context);
}
if (traceRegion) {
context.popTraceRegion(traceRegion);
const parentsFileURI = context.getParentTraceSourceFileURI();
if (parentsFileURI && sourceRegion?.fileURI === parentsFileURI) {
// if some parent's sourceRegion refers to the same source file uri (and no other source file was referenced inbetween)
// we can drop the file uri in order to reduce repeated strings
delete sourceRegion.fileURI;
}
traceRegion.complete && traceRegion.complete(context.currentPosition);
}
}
function processIndentNode(node, context) {
if (hasContent(node, context)) {
if (node.indentImmediately && !context.pendingIndent) {
context.append(node.indentation ?? context.defaultIndentation, true);
}
try {
context.increaseIndent(node);
processCompositeNode(node, context);
}
finally {
context.decreaseIndent();
}
}
}
function processNewLineNode(node, context) {
if (node.ifNotEmpty && !hasNonWhitespace(context.currentLineContent)) {
context.resetCurrentLine();
}
else {
handlePendingIndent(context, true);
let count = node.count;
while (count-- > 0) {
context.append(node.lineDelimiter);
context.addNewLine();
}
}
}
function hasNonWhitespace(text) {
return text.trimStart() !== '';
}
//# sourceMappingURL=node-processor.js.map
File diff suppressed because one or more lines are too long
+280
View File
@@ -0,0 +1,280 @@
/******************************************************************************
* Copyright 2022 TypeFox GmbH
* This program and the accompanying materials are made available under the
* terms of the MIT License, which is available in the project root.
******************************************************************************/
import type { AstNode, Properties } from '../syntax-tree.js';
import { CompositeGeneratorNode } from './generator-node.js';
import type { SourceRegion } from './generator-tracing.js';
/**
* A tag function that attaches the template's content to a {@link CompositeGeneratorNode}.
*
* This is done segment by segment, and static template portions as well as substitutions
* are added individually to the returned {@link CompositeGeneratorNode}.
* At that common leading indentation of all the template's static parts is trimmed,
* whereas additional indentations of particular lines within that static parts as well as
* any line breaks and indentation within the substitutions are kept.
*
* For the sake of good readability and good composability of results of this function like
* in the following example, the subsequent rule is applied.
*
* ```ts
* expandToNode`
* This is the beginning of something
*
* ${foo.bar ? expandToNode`
* bla bla bla ${foo.bar}
*
* `: undefined}
* end of something
* `
* ```
*
* Rule:
* In case of a multiline template
* 1. the content of the first line including its terminating line break is ignored,
* if and only if it is empty or contains whitespace only.
* 2. the content of the last line including its preceding line break (last line within the template excluding the trailing backtick) is ignored,
* if and only if it is empty or contains whitespace only, and the whitespace in-between the last line break and the trailing backtick
* is a real prefix of the common indentation among all none-empty lines of the template, i.e.,
* it is shorter than the common indentation that is trimmed during processing the template provided that common indentation is identifiable.
*
* Thus, the results of all of the following invocations are identical and equal to `generatedContent`.
* ```ts
* expandToNode`generatedContent`
* expandToNode`
* generatedContent`
* expandToNode`
* generatedContent
* `
* expandToNode`
* generatedContent
* `
* ```
* In contrast, the results of the following invocations are equal to `generatedContent\n` (or `generatedContent\r\n` on MS Windows)
* ```ts
* expandToNode`generatedContent
* `
* expandToNode`
* generatedContent
* `
* ```
*
* In addition, a third rule is applied while processing line breaks:
* If a line's last substitution contributes `undefined` or an object of type {@link GeneratorNode},
* the subsequent line break will be appended via {@link CompositeGeneratorNode.appendNewLineIfNotEmpty}.
* Hence, if all other segments of that line contribute whitespace characters only,
* the entire line will be omitted while rendering the desired output.
* Otherwise, linebreaks will be added via {@link CompositeGeneratorNode.appendNewLine}.
* That holds in particular, if the last substitution contributes an empty string. In consequence,
* adding `${''}` to the end of a line consisting of whitespace and substitutions only
* enforces the line break to be rendered, no matter what the substitutions actually contribute.
*
* @param staticParts the static parts of a tagged template literal
* @param substitutions the variable parts of a tagged template literal
* @returns a 'CompositeGeneratorNode' containing the particular aligned lines
* after resolving and inserting the substitutions into the given parts
*/
export declare function expandToNode(staticParts: TemplateStringsArray, ...substitutions: unknown[]): CompositeGeneratorNode;
/**
* Convenience function for creating a {@link CompositeGeneratorNode} being configured with the
* provided tracing information in form of `{astNode, property?, index: undefined}` and appending content
* in form of a template.
*
* This function returns a tag function that takes the desired template and does the processing
* by delegating to {@link expandToNode} and {@link traceToNode} and finally returning the
* resulting generator node.
*
* @param astNode the AstNode corresponding to the appended content
*
* @param property the value property name (string) corresponding to the appended content,
* if e.g. the content corresponds to some `string` or `number` property of `astNode`, is optional
*
* @returns a tag function behaving as described above, which in turn returns a {@link CompositeGeneratorNode}.
*
* @example
* expandTracedToNode(entity)`
* Hello ${ traceToNode(entity, 'name')(entity.name) }
* `.appendNewLine()
*/
export declare function expandTracedToNode<T extends AstNode>(astNode: T, property?: Properties<T>): (staticParts: TemplateStringsArray, ...substitutions: unknown[]) => CompositeGeneratorNode;
/**
* Convenience function for creating a {@link CompositeGeneratorNode} being configured with the
* provided tracing information in form of `{astNode, property, index}` and appending content
* in form of a template.
*
* This function returns a tag function that takes the desired template and does the processing
* by delegating to {@link expandToNode} and {@link traceToNode} and finally returning the
* resulting generator node.
*
* @param astNode the AstNode corresponding to the appended content
*
* @param property the value property name (string) corresponding to the appended content,
* if e.g. the content corresponds to some `string` or `number` property of `astNode`
*
* @param index the index of the value within a list property corresponding to the appended content,
* if the property contains a list of elements, is ignored otherwise
*
* @returns a tag function behaving as described above, which in turn returns a {@link CompositeGeneratorNode}.
*
* @example
* expandTracedToNode(entity, 'definitions', 0)`
* Hello ${ traceToNode(entity, 'name')(entity.name) }
* `.appendNewLine()
*/
export declare function expandTracedToNode<T extends AstNode>(astNode: T, property: Properties<T>, index?: number | undefined): (staticParts: TemplateStringsArray, ...substitutions: unknown[]) => CompositeGeneratorNode;
/**
* Convenience function for creating a {@link CompositeGeneratorNode} being configured with the
* provided tracing information in form of concrete coordinates and appending content
* in form of a template. Complete coordinates are provided by the {@link AstNode AstNodes}'
* corresponding {@link AstNode.$cstNode AstNode.$cstNodes}.
*
* This function returns a tag function that takes the desired template and does the processing
* by delegating to {@link expandToNode} and {@link traceToNode} and finally returning the
* resulting generator node.
*
* @param sourceRegion a text region within some file in form of concrete coordinates,
* if `undefined` no tracing will happen
*
* @returns a tag function behaving as described above, which in turn returns a {@link CompositeGeneratorNode}.
*
* @example
* expandTracedToNode(entity.$cstNode)`
* Hello ${ traceToNode(entity, 'name')(entity.name) }
* `.appendNewLine()
*/
export declare function expandTracedToNode(sourceRegion: SourceRegion | undefined): (staticParts: TemplateStringsArray, ...substitutions: unknown[]) => CompositeGeneratorNode;
/**
* Convenience function for creating a {@link CompositeGeneratorNode} being configured with the
* provided tracing information in form of a list of concrete coordinates and appending content
* in form of a template. Complete coordinates are provided by the {@link AstNode AstNodes}'
* corresponding {@link AstNode.$cstNode AstNode.$cstNodes}.
*
* This function returns a tag function that takes the desired template and does the processing
* by delegating to {@link expandToNode} and {@link traceToNode} and finally returning the
* resulting generator node.
*
* The list of regions in `sourceRegions` will later be reduced to the smallest encompassing region
* of all the contained source regions.
*
* @param sourceRegions a list of text regions within some file in form of concrete coordinates,
* if empty no tracing will happen
*
* @returns a tag function behaving as described above, which in turn returns a {@link CompositeGeneratorNode}.
*
* @example
* expandTracedToNode([
* findNodeForKeyword(entity.$cstNode, '{')!,
* findNodeForKeyword(entity.$cstNode, '}')!
* ])`
* Hello ${ traceToNode(entity, 'name')(entity.name) }
* `.appendNewLine()
*/
export declare function expandTracedToNode(sourceRegions: SourceRegion[]): (staticParts: TemplateStringsArray, ...substitutions: unknown[]) => CompositeGeneratorNode;
/**
* Convenience function for creating a {@link CompositeGeneratorNode} being configured with the
* provided tracing information in form of `{astNode, property?, index: undefined}` and appending content
* in form of a template, if `condition` is equal to `true`.
*
* If `condition` is satisfied, this function returns a tag function that takes the desired template
* and does the processing by delegating to {@link expandToNode} and {@link traceToNode} and
* finally returning the resulting generator node. Otherwise, the returned function just returns `undefined`.
*
* @param condition a boolean value indicating whether to evaluate the provided template.
*
* @param astNode the AstNode corresponding to the appended content
*
* @param property the value property name (string) corresponding to the appended content,
* if e.g. the content corresponds to some `string` or `number` property of `astNode`, is optional
*
* @returns a tag function behaving as described above, which in turn returns a {@link CompositeGeneratorNode}.
*
* @example
* expandTracedToNodeIf(entity !== undefined, entity)`
* Hello ${ traceToNode(entity, 'name')(entity.name) }
* `.appendNewLine()
*/
export declare function expandTracedToNodeIf<T extends AstNode>(condition: boolean, astNode: T, property?: Properties<T>): (staticParts: TemplateStringsArray, ...substitutions: unknown[]) => CompositeGeneratorNode | undefined;
/**
* Convenience function for creating a {@link CompositeGeneratorNode} being configured with the
* provided tracing information in form of `{astNode, property, index}` and appending content
* in form of a template, if `condition` is equal to `true`.
*
* If `condition` is satisfied, this function returns a tag function that takes the desired template
* and does the processing by delegating to {@link expandToNode} and {@link traceToNode} and
* finally returning the resulting generator node. Otherwise, the returned function just returns `undefined`.
*
* @param condition a boolean value indicating whether to evaluate the provided template.
*
* @param astNode the AstNode corresponding to the appended content
*
* @param property the value property name (string) corresponding to the appended content,
* if e.g. the content corresponds to some `string` or `number` property of `astNode`
*
* @param index the index of the value within a list property corresponding to the appended content,
* if the property contains a list of elements, is ignored otherwise
*
* @returns a tag function behaving as described above, which in turn returns a {@link CompositeGeneratorNode}.
*
* @example
* expandTracedToNodeIf(entity !== undefined, entity, 'definitions', 0)`
* Hello ${ traceToNode(entity, 'name')(entity.name) }
* `.appendNewLine()
*/
export declare function expandTracedToNodeIf<T extends AstNode>(condition: boolean, astNode: T, property: Properties<T>, index: number | undefined): (staticParts: TemplateStringsArray, ...substitutions: unknown[]) => CompositeGeneratorNode | undefined;
/**
* Convenience function for creating a {@link CompositeGeneratorNode} being configured with the
* provided tracing information in form of concrete coordinates and appending content in form
* of a template, if `condition` is equal to `true`. Complete coordinates are provided by
* the {@link AstNode AstNodes}' corresponding {@link AstNode.$cstNode AstNode.$cstNodes}.
*
* This function returns a tag function that takes the desired template and does the processing
* by delegating to {@link expandToNode} and {@link traceToNode} and finally returning the
* resulting generator node.
*
* If `sourceRegion` is a function supplying the corresponding region, it's only called if `condition` is satisfied.
*
* @param condition a boolean value indicating whether to evaluate the provided template.
*
* @param sourceRegion a text region within some file in form of concrete coordinates or a supplier function,
* if `undefined` no tracing will happen
*
* @returns a tag function behaving as described above, which in turn returns a {@link CompositeGeneratorNode}.
*
* @example
* expandTracedToNodeIf(entity !== undefined, entity.$cstNode)`
* Hello ${ traceToNode(entity, 'name')(entity.name) }
* `.appendNewLine()
*/
export declare function expandTracedToNodeIf(condition: boolean, sourceRegion: SourceRegion | undefined | (() => SourceRegion | undefined)): (staticParts: TemplateStringsArray, ...substitutions: unknown[]) => CompositeGeneratorNode | undefined;
/**
* Convenience function for creating a {@link CompositeGeneratorNode} being configured with the
* provided tracing information in form of a list of concrete coordinates and appending content
* in form of a template, if `condition` is equal to `true`. Complete coordinates are provided
* by the {@link AstNode AstNodes}' corresponding {@link AstNode.$cstNode AstNode.$cstNodes}.
*
* This function returns a tag function that takes the desired template and does the processing
* by delegating to {@link expandToNode} and {@link traceToNode} and finally returning the
* resulting generator node.
*
* The list of regions in `sourceRegions` will later be reduced to the smallest encompassing region
* of all the contained source regions.
* If `sourceRegions` is a function supplying the corresponding regions, it's only called if `condition` is satisfied.
*
* @param condition a boolean value indicating whether to evaluate the provided template.
*
* @param sourceRegions a list of text regions within some file in form of concrete coordinates,
* if empty no tracing will happen
*
* @returns a tag function behaving as described above, which in turn returns a {@link CompositeGeneratorNode}.
*
* @example
* expandTracedToNodeIf(entity !== undefined, [
* findNodeForKeyword(entity.$cstNode, '{')!,
* findNodeForKeyword(entity.$cstNode, '}')!
* ])`
* Hello ${ traceToNode(entity, 'name')(entity.name) }
* `.appendNewLine()
*/
export declare function expandTracedToNodeIf(condition: boolean, sourceRegions: SourceRegion[]): (staticParts: TemplateStringsArray, ...substitutions: unknown[]) => CompositeGeneratorNode | undefined;
//# sourceMappingURL=template-node.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"template-node.d.ts","sourceRoot":"","sources":["../../src/generate/template-node.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAEhF,OAAO,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAG7D,OAAO,EAAE,sBAAsB,EAAgC,MAAM,qBAAqB,CAAC;AAC3F,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAG3D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoEG;AACH,wBAAgB,YAAY,CAAC,WAAW,EAAE,oBAAoB,EAAE,GAAG,aAAa,EAAE,OAAO,EAAE,GAAG,sBAAsB,CAUnH;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,QAAQ,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,WAAW,EAAE,oBAAoB,EAAE,GAAG,aAAa,EAAE,OAAO,EAAE,KAAK,sBAAsB,CAAC;AAExL;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,CAAC,WAAW,EAAE,oBAAoB,EAAE,GAAG,aAAa,EAAE,OAAO,EAAE,KAAK,sBAAsB,CAAC;AAEnN;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,kBAAkB,CAAC,YAAY,EAAE,YAAY,GAAG,SAAS,GAAG,CAAC,WAAW,EAAE,oBAAoB,EAAE,GAAG,aAAa,EAAE,OAAO,EAAE,KAAK,sBAAsB,CAAC;AAEvK;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,kBAAkB,CAAC,aAAa,EAAE,YAAY,EAAE,GAAG,CAAC,WAAW,EAAE,oBAAoB,EAAE,GAAG,aAAa,EAAE,OAAO,EAAE,KAAK,sBAAsB,CAAC;AAW9J;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,SAAS,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,QAAQ,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,GACxG,CAAC,WAAW,EAAE,oBAAoB,EAAE,GAAG,aAAa,EAAE,OAAO,EAAE,KAAK,sBAAsB,GAAG,SAAS,CAAC;AAE/G;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,SAAS,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,GAClI,CAAC,WAAW,EAAE,oBAAoB,EAAE,GAAG,aAAa,EAAE,OAAO,EAAE,KAAK,sBAAsB,GAAG,SAAS,CAAC;AAE/G;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,OAAO,EAAE,YAAY,EAAE,YAAY,GAAG,SAAS,GAAG,CAAC,MAAM,YAAY,GAAG,SAAS,CAAC,GAC1H,CAAC,WAAW,EAAE,oBAAoB,EAAE,GAAG,aAAa,EAAE,OAAO,EAAE,KAAK,sBAAsB,GAAG,SAAS,CAAC;AAE/G;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,GAC9E,CAAC,WAAW,EAAE,oBAAoB,EAAE,GAAG,aAAa,EAAE,OAAO,EAAE,KAAK,sBAAsB,GAAG,SAAS,CAAC"}
+301
View File
@@ -0,0 +1,301 @@
/******************************************************************************
* Copyright 2022 TypeFox GmbH
* This program and the accompanying materials are made available under the
* terms of the MIT License, which is available in the project root.
******************************************************************************/
import { NEWLINE_REGEXP } from '../utils/regexp-utils.js';
import { CompositeGeneratorNode, isGeneratorNode, traceToNode } from './generator-node.js';
import { findIndentation } from './template-string.js';
/**
* A tag function that attaches the template's content to a {@link CompositeGeneratorNode}.
*
* This is done segment by segment, and static template portions as well as substitutions
* are added individually to the returned {@link CompositeGeneratorNode}.
* At that common leading indentation of all the template's static parts is trimmed,
* whereas additional indentations of particular lines within that static parts as well as
* any line breaks and indentation within the substitutions are kept.
*
* For the sake of good readability and good composability of results of this function like
* in the following example, the subsequent rule is applied.
*
* ```ts
* expandToNode`
* This is the beginning of something
*
* ${foo.bar ? expandToNode`
* bla bla bla ${foo.bar}
*
* `: undefined}
* end of something
* `
* ```
*
* Rule:
* In case of a multiline template
* 1. the content of the first line including its terminating line break is ignored,
* if and only if it is empty or contains whitespace only.
* 2. the content of the last line including its preceding line break (last line within the template excluding the trailing backtick) is ignored,
* if and only if it is empty or contains whitespace only, and the whitespace in-between the last line break and the trailing backtick
* is a real prefix of the common indentation among all none-empty lines of the template, i.e.,
* it is shorter than the common indentation that is trimmed during processing the template provided that common indentation is identifiable.
*
* Thus, the results of all of the following invocations are identical and equal to `generatedContent`.
* ```ts
* expandToNode`generatedContent`
* expandToNode`
* generatedContent`
* expandToNode`
* generatedContent
* `
* expandToNode`
* generatedContent
* `
* ```
* In contrast, the results of the following invocations are equal to `generatedContent\n` (or `generatedContent\r\n` on MS Windows)
* ```ts
* expandToNode`generatedContent
* `
* expandToNode`
* generatedContent
* `
* ```
*
* In addition, a third rule is applied while processing line breaks:
* If a line's last substitution contributes `undefined` or an object of type {@link GeneratorNode},
* the subsequent line break will be appended via {@link CompositeGeneratorNode.appendNewLineIfNotEmpty}.
* Hence, if all other segments of that line contribute whitespace characters only,
* the entire line will be omitted while rendering the desired output.
* Otherwise, linebreaks will be added via {@link CompositeGeneratorNode.appendNewLine}.
* That holds in particular, if the last substitution contributes an empty string. In consequence,
* adding `${''}` to the end of a line consisting of whitespace and substitutions only
* enforces the line break to be rendered, no matter what the substitutions actually contribute.
*
* @param staticParts the static parts of a tagged template literal
* @param substitutions the variable parts of a tagged template literal
* @returns a 'CompositeGeneratorNode' containing the particular aligned lines
* after resolving and inserting the substitutions into the given parts
*/
export function expandToNode(staticParts, ...substitutions) {
// first part: determine the common indentation of all the template lines with the substitutions being ignored
const templateProps = findIndentationAndTemplateStructure(staticParts);
// 2nd part: for all the static template parts: split them and inject a NEW_LINE marker where line breaks shall be a present in the final result,
// and create a flatten list of strings, NEW_LINE marker occurrences, and substitutions
const splitAndMerged = splitTemplateLinesAndMergeWithSubstitutions(staticParts, substitutions, templateProps);
// eventually, inject indentation nodes and append the segments to final desired composite generator node
return composeFinalGeneratorNode(splitAndMerged);
}
// implementation:
export function expandTracedToNode(source, property, index) {
return (staticParts, ...substitutions) => {
return traceToNode(source, property, index)(expandToNode(staticParts, ...substitutions));
};
}
// implementation:
export function expandTracedToNodeIf(condition, source, property, index) {
return condition ? expandTracedToNode((typeof source === 'function' ? source() : source), property, index) : () => undefined;
}
function findIndentationAndTemplateStructure(staticParts) {
const lines = staticParts.join('_').split(NEWLINE_REGEXP);
const omitFirstLine = lines.length > 1 && lines[0].trim().length === 0;
const omitLastLine = omitFirstLine && lines.length > 1 && lines[lines.length - 1].trim().length === 0;
if (lines.length === 1 || lines.length !== 0 && lines[0].trim().length !== 0 || lines.length === 2 && lines[1].trim().length === 0) {
// for cases of non-adjusted templates like
// const n1 = expandToNode` `;
// const n2 = expandToNode` something `;
// const n3 = expandToNode` something
// `;
// ... consider the indentation to be empty, and all the leading whitespace to be relevant, except for the last (empty) line of n3!
return {
indentation: 0, //''
omitFirstLine,
omitLastLine,
trimLastLine: lines.length !== 1 && lines[lines.length - 1].trim().length === 0
};
}
else {
// otherwise:
// for cases of non-adjusted templates like
// const n4 = expandToNode` abc
// def `;
// const n5 = expandToNode`<maybe with some WS here>
// abc
// def`;
// const n6 = expandToNode`<maybe with some WS here>
// abc
// def
// `;
// ... the indentation shall be determined by the non-empty lines, excluding the last line if it contains whitespace only
// if we have a multi-line template and the first line is empty, see n5, n6
// ignore the first line;
let sliced = omitFirstLine ? lines.slice(1) : lines;
// if there're more than one line remaining and the last one only contains WS, see n6,
// ignore the last line
sliced = omitLastLine ? sliced.slice(0, sliced.length - 1) : sliced;
// ignore empty lines during indentation calculation, as linting rules might forbid lines containing just whitespace
sliced = sliced.filter(e => e.length !== 0);
const indentation = findIndentation(sliced);
return {
indentation,
omitFirstLine,
// in the subsequent steps omit the last line only if it is empty or if it only contains whitespace of which the common indentation is not a valid prefix;
// in other words: keep the last line if it matches the common indentation (and maybe contains non-whitespace), a non-match may be due to mistaken usage of tabs and spaces
omitLastLine: omitLastLine && (lines[lines.length - 1].length < indentation || !lines[lines.length - 1].startsWith(sliced[0].substring(0, indentation)))
};
}
}
function splitTemplateLinesAndMergeWithSubstitutions(staticParts, substitutions, { indentation, omitFirstLine, omitLastLine, trimLastLine }) {
const splitAndMerged = [];
staticParts.forEach((part, i) => {
splitAndMerged.push(...part.split(NEWLINE_REGEXP).map((e, j) => j === 0 || e.length < indentation ? e : e.substring(indentation)).reduce(
// treat the particular (potentially multiple) lines of the <i>th template segment (part),
// s.t. all the effective lines are collected and separated by the NEWLINE node
// note: different reduce functions are provided for the initial template segment vs. the remaining segments
i === 0
? (result, line, j) =>
// special handling of the initial template segment, which may contain line-breaks;
// suppresses the injection of unintended NEWLINE indicators for templates like
// expandToNode`
// someText
// ${something}
// `
j === 0
? (omitFirstLine // for templates with empty first lines like above (expandToNode`\n ...`)
? [] // skip adding the initial line
: [line] // take the initial line if non-empty
)
: (j === 1 && result.length === 0 // when looking on the 2nd line in case the first line (in the first segment) is skipped ('result' is still empty)
? [line] // skip the insertion of the NEWLINE marker and just return the current line
: result.concat(NEWLINE, line) // otherwise append the NEWLINE marker and the current line
)
: (result, line, j) =>
// handling of the remaining template segments
j === 0 ? [line] : result.concat(NEWLINE, line) // except for the first line in the current segment prepend each line with NEWLINE
, [] // start with an empty array
).filter(e => !(typeof e === 'string' && e.length === 0) // drop empty strings, they don't contribute anything but might confuse subsequent processing
).concat(
// append the corresponding substitution after each segment (part),
// note that 'substitutions[i]' will be undefined for the last segment
isGeneratorNode(substitutions[i])
// if the substitution is a generator node, take it as it is
? substitutions[i]
: substitutions[i] !== undefined
// if the substitution is something else, convert it to a string and wrap it;
// allows us below to distinguish template strings from substitution (esp. empty) ones
? { content: String(substitutions[i]) }
: i < substitutions.length
// if 'substitutions[i]' is undefined and we are treating a substitution "in the middle"
// we found a substitution that is assumed to not contribute anything on purpose!
? UNDEFINED_SEGMENT // add a corresponding marker, see below for details on the rational
: [] /* don't concat anything as we passed behind the last substitution, since 'i' enumerates the indices of 'staticParts',
but 'substitutions' has one entry less and 'substitutions[staticParts.length -1 ]' will always be undefined */));
});
// for templates like
// expandToNode`
// someText
// `
// TODO add more documentation here
const splitAndMergedLength = splitAndMerged.length;
const lastItem = splitAndMergedLength !== 0 ? splitAndMerged[splitAndMergedLength - 1] : undefined;
if ((omitLastLine || trimLastLine) && typeof lastItem === 'string' && lastItem.trim().length === 0) {
if (omitFirstLine && splitAndMergedLength !== 1 && splitAndMerged[splitAndMergedLength - 2] === NEWLINE) {
return splitAndMerged.slice(0, splitAndMergedLength - 2);
}
else {
return splitAndMerged.slice(0, splitAndMergedLength - 1);
}
}
else {
return splitAndMerged;
}
}
const NEWLINE = { isNewLine: true };
const UNDEFINED_SEGMENT = { isUndefinedSegment: true };
const isNewLineMarker = (nl) => nl === NEWLINE;
const isUndefinedSegmentMarker = (us) => us === UNDEFINED_SEGMENT;
const isSubstitutionWrapper = (s) => s.content !== undefined;
function composeFinalGeneratorNode(splitAndMerged) {
// in order to properly handle the indentation of nested multi-line substitutions,
// track the length of static (string) parts per line and wrap the substitution(s) in indentation nodes, if needed
//
// of course, this only works nicely if a multi-line substitution is preceded by static string parts on the same line only;
// in case of dynamic content (with a potentially unknown length) followed by a multi-line substitution
// the latter's indentation cannot be determined properly...
const result = splitAndMerged.reduce((res, segment, i) => isUndefinedSegmentMarker(segment)
// ignore all occurrences of UNDEFINED_SEGMENT, they are just in there for the below test
// of 'isNewLineMarker(splitAndMerged[i-1])' not to evaluate to 'truthy' in case of consecutive lines
// with no actual content in templates like
// expandToNode`
// Foo
// ${undefined} <<----- here
// ${undefined} <<----- and here
//
// Bar
// `
? res
: isNewLineMarker(segment)
? {
// in case of a newLine marker append an 'ifNotEmpty' newLine by default, but
// append an unconditional newLine if and only if:
// * the template starts with the current line break, i.e. the first line is empty
// * the current newLine marker directly follows another one, i.e. the current line is empty
// * the current newline marker directly follows a substitution contributing a string (or some non-GeneratorNode being converted to a string)
// * the current newline marker directly follows a (template static) string that
// * is the initial token of the template
// * is the initial token of the line, maybe just indentation
// * follows a a substitution contributing a string (or some non-GeneratorNode being converted to a string), maybe is just irrelevant trailing whitespace
// in particular do _not_ append an unconditional newLine if the last substitution of a line contributes 'undefined' or an instance of 'GeneratorNode'
// which may be a newline itself or be empty or (transitively) contain a trailing newline itself
// node: i === 0
// || isNewLineMarker(splitAndMerged[i - 1]) || isSubstitutionWrapper(splitAndMerged[i - 1]) /* implies: typeof content === 'string', esp. !undefined */
// || typeof splitAndMerged[i - 1] === 'string' && (
// i === 1 || isNewLineMarker(splitAndMerged[i - 2]) || isSubstitutionWrapper(splitAndMerged[i - 2]) /* implies: typeof content === 'string', esp. !undefined */
// )
// ? res.node.appendNewLine() : res.node.appendNewLineIfNotEmpty()
//
// UPDATE cs: inverting the logic leads to the following, I hope I didn't miss anything:
// in case of a newLine marker append an unconditional newLine by default, but
// append an 'ifNotEmpty' newLine if and only if:
// * the template doesn't start with a newLine marker and
// * the current newline marker directly follows a substitution contributing an `undefined` or an instance of 'GeneratorNode', or
// * the current newline marker directly follows a (template static) string (containing potentially unintended trailing whitespace)
// that in turn directly follows a substitution contributing an `undefined` or an instance of 'GeneratorNode'
node: i !== 0 && (isUndefinedSegmentMarker(splitAndMerged[i - 1]) || isGeneratorNode(splitAndMerged[i - 1]))
|| i > 1 && typeof splitAndMerged[i - 1] === 'string' && (isUndefinedSegmentMarker(splitAndMerged[i - 2]) || isGeneratorNode(splitAndMerged[i - 2]))
? res.node.appendNewLineIfNotEmpty() : res.node.appendNewLine()
} : (() => {
// the indentation handling is supposed to handle use cases like
// bla bla bla {
// ${foo(bar)}
// }
// and
// bla bla bla {
// return ${foo(bar)}
// }
// assuming that ${foo(bar)} yields a multiline result;
// the whitespace between 'return' and '${foo(bar)}' shall not add to the indentation of '${foo(bar)}'s result!
const indent = (i === 0 || isNewLineMarker(splitAndMerged[i - 1])) && typeof segment === 'string' && segment.length !== 0 ? segment.substring(0, segment.length - segment.trimStart().length) : '';
const content = isSubstitutionWrapper(segment) ? segment.content : segment;
let indented;
return {
node: res.indented
// in case an indentNode has been registered earlier for the current line,
// just return 'node' without manipulation, the current segment will be added to the indentNode
? res.node
// otherwise (no indentNode is registered by now)...
: indent.length !== 0
// in case an indentation has been identified add a non-immediate indentNode to 'node' and
// add the current segment (containing its the indentation) to that indentNode,
// and keep the indentNode in a local variable 'indented' for registering below,
// and return 'node'
? res.node.indent({ indentation: indent, indentImmediately: false, indentedChildren: ind => indented = ind.append(content) })
// otherwise just add the content to 'node' and return it
: res.node.append(content),
indented:
// if an indentNode has been created in this cycle, just register it,
// otherwise check for a earlier registered indentNode and add the current segment to that one
indented ?? res.indented?.append(content),
};
})(), { node: new CompositeGeneratorNode() });
return result.node;
}
//# sourceMappingURL=template-node.js.map
File diff suppressed because one or more lines are too long
+29
View File
@@ -0,0 +1,29 @@
/******************************************************************************
* Copyright 2021 TypeFox GmbH
* This program and the accompanying materials are made available under the
* terms of the MIT License, which is available in the project root.
******************************************************************************/
export declare function expandToStringWithNL(staticParts: TemplateStringsArray, ...substitutions: unknown[]): string;
export declare function expandToStringLFWithNL(staticParts: TemplateStringsArray, ...substitutions: unknown[]): string;
/**
* A tag function that automatically aligns embedded multiline strings.
* Multiple lines are joined with the platform-specific line separator.
*
* @param staticParts the static parts of a tagged template literal
* @param substitutions the variable parts of a tagged template literal
* @returns an aligned string that consists of the given parts
*/
export declare function expandToString(staticParts: TemplateStringsArray, ...substitutions: unknown[]): string;
/**
* A tag function that automatically aligns embedded multiline strings.
* Multiple lines are joined with the LINE_FEED (`\n`) line separator.
*
* @param staticParts the static parts of a tagged template literal
* @param substitutions the variable parts of a tagged template literal
* @returns an aligned string that consists of the given parts
*/
export declare function expandToStringLF(staticParts: TemplateStringsArray, ...substitutions: unknown[]): string;
export declare const SNLE: string;
export declare function findIndentation(lines: string[]): number;
export declare function normalizeEOL(input: string): string;
//# sourceMappingURL=template-string.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"template-string.d.ts","sourceRoot":"","sources":["../../src/generate/template-string.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAKhF,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,oBAAoB,EAAE,GAAG,aAAa,EAAE,OAAO,EAAE,GAAG,MAAM,CAE3G;AAED,wBAAgB,sBAAsB,CAAC,WAAW,EAAE,oBAAoB,EAAE,GAAG,aAAa,EAAE,OAAO,EAAE,GAAG,MAAM,CAE7G;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,WAAW,EAAE,oBAAoB,EAAE,GAAG,aAAa,EAAE,OAAO,EAAE,GAAG,MAAM,CAErG;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,oBAAoB,EAAE,GAAG,aAAa,EAAE,OAAO,EAAE,GAAG,MAAM,CAEvG;AAyCD,eAAO,MAAM,IAAI,QAAgD,CAAC;AAWlE,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,CAIvD;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAElD"}
+87
View File
@@ -0,0 +1,87 @@
/******************************************************************************
* Copyright 2021 TypeFox GmbH
* This program and the accompanying materials are made available under the
* terms of the MIT License, which is available in the project root.
******************************************************************************/
import { NEWLINE_REGEXP } from '../utils/regexp-utils.js';
import { EOL, toString } from './generator-node.js';
export function expandToStringWithNL(staticParts, ...substitutions) {
return expandToString(staticParts, ...substitutions) + EOL;
}
export function expandToStringLFWithNL(staticParts, ...substitutions) {
return expandToStringLF(staticParts, ...substitutions) + '\n';
}
/**
* A tag function that automatically aligns embedded multiline strings.
* Multiple lines are joined with the platform-specific line separator.
*
* @param staticParts the static parts of a tagged template literal
* @param substitutions the variable parts of a tagged template literal
* @returns an aligned string that consists of the given parts
*/
export function expandToString(staticParts, ...substitutions) {
return internalExpandToString(EOL, staticParts, ...substitutions);
}
/**
* A tag function that automatically aligns embedded multiline strings.
* Multiple lines are joined with the LINE_FEED (`\n`) line separator.
*
* @param staticParts the static parts of a tagged template literal
* @param substitutions the variable parts of a tagged template literal
* @returns an aligned string that consists of the given parts
*/
export function expandToStringLF(staticParts, ...substitutions) {
return internalExpandToString('\n', staticParts, ...substitutions);
}
function internalExpandToString(lineSep, staticParts, ...substitutions) {
let lines = substitutions
// align substitutions and fuse them with static parts
.reduce((acc, subst, i) => acc + (subst === undefined ? SNLE : align(toString(subst), acc)) + (staticParts[i + 1] ?? ''), staticParts[0])
// converts text to lines
.split(NEWLINE_REGEXP)
.filter(l => l.trim() !== SNLE)
// whitespace-only lines are empty (preserving leading whitespace)
.map(l => l.replace(SNLE, '').trimEnd());
// in order to nicely handle single line templates with the leading and trailing termintators (``) on separate lines, like
// expandToString`foo
// `,
// expandToString`
// foo
// `,
// expandToString`
// foo`,
// the same way as true single line templates like
// expandToString`foo`
// ...
// ... drop initial linebreak if the first line is empty or contains white space only, ...
const containsLeadingLinebreak = lines.length > 1 && lines[0].trim().length === 0;
lines = containsLeadingLinebreak ? lines.slice(1) : lines;
// .. and drop the last linebreak if it's the last charactor or is followed by white space
const containsTrailingLinebreak = lines.length !== 0 && lines[lines.length - 1].trimEnd().length === 0;
lines = containsTrailingLinebreak ? lines.slice(0, lines.length - 1) : lines;
// finds the minimum indentation
const indent = findIndentation(lines);
return lines
// shifts lines to the left
.map(line => line.slice(indent).trimEnd())
// convert lines to string
.join(lineSep);
}
export const SNLE = Object.freeze('__«SKIP^NEW^LINE^IF^EMPTY»__');
const nonWhitespace = /\S|$/;
// add the alignment of the previous static part to all lines of the following substitution
function align(subst, acc) {
const length = Math.max(0, acc.length - acc.lastIndexOf('\n') - 1);
const indent = ' '.repeat(length);
return subst.replace(NEWLINE_REGEXP, EOL + indent);
}
// finds the indentation of a text block represented by a sequence of lines
export function findIndentation(lines) {
const indents = lines.filter(line => line.length > 0).map(line => line.search(nonWhitespace));
const min = indents.length === 0 ? 0 : Math.min(...indents); // min(...[]) = min() = Infinity
return Math.max(0, min);
}
export function normalizeEOL(input) {
return input.replace(NEWLINE_REGEXP, EOL);
}
//# sourceMappingURL=template-string.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"template-string.js","sourceRoot":"","sources":["../../src/generate/template-string.ts"],"names":[],"mappings":"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"}