@remotex-labs/xmap
Version:
A library with a sourcemap parser and TypeScript code formatter for the CLI
400 lines (399 loc) • 15.1 kB
JavaScript
// src/components/highlighter.component.ts
import t from "typescript";
import { SyntaxKind as c } from "typescript";
import { xterm as i } from "@remotex-labs/xansi/xterm.component";
var m = {
enumColor: i.burntOrange,
typeColor: i.lightGoldenrodYellow,
classColor: i.lightOrange,
stringColor: i.oliveGreen,
keywordColor: i.lightCoral,
commentColor: i.darkGray,
functionColor: i.lightOrange,
variableColor: i.burntOrange,
interfaceColor: i.lightGoldenrodYellow,
parameterColor: i.deepOrange,
getAccessorColor: i.lightYellow,
numericLiteralColor: i.lightGray,
methodSignatureColor: i.burntOrange,
regularExpressionColor: i.oliveGreen,
propertyAssignmentColor: i.canaryYellow,
propertyAccessExpressionColor: i.lightYellow,
expressionWithTypeArgumentsColor: i.lightOrange
}, l = class {
/**
* Creates an instance of the CodeHighlighter class.
*
* @param sourceFile - The TypeScript AST node representing the source file
* @param code - The source code string to be highlighted
* @param schema - The color scheme used for highlighting different elements in the code
*
* @since 1.0.0
*/
constructor(e, r, n) {
this.sourceFile = e;
this.code = r;
this.schema = n;
}
/**
* A Map of segments where the key is a combination of start and end positions.
*
* @remarks
* This structure ensures unique segments and allows for fast lookups and updates.
*
* @see HighlightNodeSegmentInterface
* @since 1.0.0
*/
segments = /* @__PURE__ */ new Map();
/**
* Parses a TypeScript AST node and processes its comments to identify segments that need highlighting.
*
* @param node - The TypeScript AST node to be parsed
*
* @since 1.0.0
*/
parseNode(e) {
this.processComments(e), this.processKeywords(e), this.processNode(e);
}
/**
* Generates a string with highlighted code segments based on the provided color scheme.
*
* @returns The highlighted code as a string, with ANSI color codes applied to the segments
*
* @remarks
* This method processes the stored segments, applies the appropriate colors to each segment,
* and returns the resulting highlighted code as a single string.
*
* @since 1.0.0
*/
highlight() {
let e = 0, r, n = [];
return Array.from(
this.segments.values()
).sort((s, a) => s.start - a.start || s.end - a.end).forEach((s) => {
if (r && s.start < r.end) {
let a = n.pop();
if (!a) return;
let d = this.getSegmentSource(s.start, s.end), g = `${s.color(d)}`;
n.push(a.replace(d, g));
return;
}
n.push(this.getSegmentSource(e, s.start)), n.push(s.color(this.getSegmentSource(s.start, s.end))), e = s.end, r = s;
}), n.join("") + this.getSegmentSource(e);
}
/**
* Extracts a text segment from the source code using position indices.
*
* @param start - The starting index position in the source text
* @param end - The ending index position in the source text (optional)
* @returns The substring of a source text between the start and end positions
*
* @remarks
* This utility method provides access to specific portions of the source code
* based on character positions. When the end parameter is omitted, the extraction
* will continue to the end of the source text.
*
* This method is typically used during the highlighting process to access the
* actual text content that corresponds to syntax nodes or other text ranges
* before applying formatting.
*
* @example
* ```ts
* // Extract a variable name
* const variableName = this.getSegmentSource(10, 15);
*
* // Extract from a position to the end of a source
* const remaining = this.getSegmentSource(100);
* ```
*
* @see addSegment
* @see highlight
*
* @since 1.0.0
*/
getSegmentSource(e, r) {
return this.code.slice(e, r);
}
/**
* Registers a text segment for syntax highlighting with specified style information.
*
* @param start - The starting position of the segment in the source text
* @param end - The ending position of the segment in the source text
* @param color - The {@link ColorFunctionType} to apply to this segment
*
* @remarks
* This method creates a unique key for each segment based on its position and stores the segment information in a map.
* Each segment contains its position information, styling code,
* and reset code which will later be used during the highlighting process.
*
* If multiple segments are added with the same positions, the later additions will
* overwrite earlier ones due to the map's key-based storage.
*
* @example
* ```ts
* // Highlight a variable name in red
* this.addSegment(10, 15, xterm.red);
*
* // Highlight a keyword with custom color
* this.addSegment(20, 26, xterm.blue);
* ```
*
* @see Colors
* @see processNode
*
* @since 1.0.0
*/
addSegment(e, r, n) {
let o = `${e}-${r}`;
this.segments.set(o, { start: e, end: r, color: n });
}
/**
* Processes and highlights comments associated with a TypeScript AST node.
*
* @param node - The TypeScript AST node whose comments are to be processed
*
* @remarks
* This method identifies both leading and trailing comments associated with the given node
* and adds them to the highlighting segments.
* The comments are extracted from the full source text using TypeScript's utility functions
* and are highlighted using the color specified
* in the schema's commentColor property.
*
* Leading comments appear before the node, while trailing comments appear after it.
* Both types are processed with the same highlighting style.
*
* @example
* ```ts
* // For a node that might have comments like:
* // This is a leading comment
* const x = 5; // This is a trailing comment
*
* this.processComments(someNode);
* // Both comments will be added to segments with the comment color
* ```
*
* @see addSegment
* @see ts.getLeadingCommentRanges
* @see ts.getTrailingCommentRanges
*
* @since 1.0.0
*/
processComments(e) {
[
...t.getTrailingCommentRanges(this.sourceFile.getFullText(), e.getFullStart()) || [],
...t.getLeadingCommentRanges(this.sourceFile.getFullText(), e.getFullStart()) || []
].forEach((n) => this.addSegment(n.pos, n.end, this.schema.commentColor));
}
/**
* Processes TypeScript keywords and primitive type references in an AST node for syntax highlighting.
*
* @param node - The TypeScript AST node to be processed for keywords
*
* @remarks
* This method handles two categories of tokens that require special highlighting:
*
* 1. Primitive type references: Highlights references to built-in types like `null`,
* `void`, `string`, `number`, `boolean`, and `undefined` using the type color.
*
* 2. TypeScript keywords: Identifies any node whose kind falls within the TypeScript
* keyword range (between FirstKeyword and LastKeyword) and highlights it using
* the keyword color.
*
* Each identified token is added to the segment collection with the appropriate position
* and color information.
*
* @example
* ```ts
* // Inside syntax highlighting process
* this.processKeywords(someNode);
* // If the node represents a keyword like 'const' or a primitive type like 'string',
* // it will be added to the segments with the appropriate color
* ```
*
* @see addSegment
* @see ts.SyntaxKind
*
* @since 1.0.0
*/
processKeywords(e) {
if ([
c.NullKeyword,
c.VoidKeyword,
c.StringKeyword,
c.NumberKeyword,
c.BooleanKeyword,
c.UndefinedKeyword
].includes(e.kind))
return this.addSegment(e.getStart(), e.getEnd(), this.schema.typeColor);
e && e.kind >= t.SyntaxKind.FirstKeyword && e.kind <= t.SyntaxKind.LastKeyword && this.addSegment(e.getStart(), e.getEnd(), this.schema.keywordColor);
}
/**
* Processes identifier nodes and applies appropriate syntax highlighting based on their context.
*
* @param node - The TypeScript AST node representing the identifier to be processed
*
* @remarks
* This method determines the appropriate color for an identifier by examining its parent node's kind.
* Different colors are applied based on the identifier's role in the code:
* - Enum members use enumColor
* - Interface names use interfaceColor
* - Class names use classColor
* - Function and method names use functionColor
* - Parameters use parameterColor
* - Variables and properties use variableColor
* - Types use typeColor
* - And more specialized cases for other syntax kinds
*
* Special handling is applied to property access expressions to differentiate between
* the object being accessed and the property being accessed.
*
* @example
* ```ts
* // Inside the CodeHighlighter class
* const identifierNode = getIdentifierNode(); // Get some identifier node
* this.processIdentifier(identifierNode);
* // The identifier is now added to segments with appropriate color based on its context
* ```
*
* @see addSegment
* @see HighlightSchemeInterface
*
* @since 1.0.0
*/
processIdentifier(e) {
let r = e.getEnd(), n = e.getStart();
switch (e.parent.kind) {
case t.SyntaxKind.EnumMember:
return this.addSegment(n, r, this.schema.enumColor);
case t.SyntaxKind.CallExpression:
case t.SyntaxKind.EnumDeclaration:
case t.SyntaxKind.PropertySignature:
case t.SyntaxKind.ModuleDeclaration:
return this.addSegment(n, r, this.schema.variableColor);
case t.SyntaxKind.InterfaceDeclaration:
return this.addSegment(n, r, this.schema.interfaceColor);
case t.SyntaxKind.GetAccessor:
return this.addSegment(n, r, this.schema.getAccessorColor);
case t.SyntaxKind.PropertyAssignment:
return this.addSegment(n, r, this.schema.propertyAssignmentColor);
case t.SyntaxKind.MethodSignature:
return this.addSegment(n, r, this.schema.methodSignatureColor);
case t.SyntaxKind.MethodDeclaration:
case t.SyntaxKind.FunctionDeclaration:
return this.addSegment(n, r, this.schema.functionColor);
case t.SyntaxKind.ClassDeclaration:
return this.addSegment(n, r, this.schema.classColor);
case t.SyntaxKind.Parameter:
return this.addSegment(n, r, this.schema.parameterColor);
case t.SyntaxKind.VariableDeclaration:
return this.addSegment(n, r, this.schema.variableColor);
case t.SyntaxKind.PropertyDeclaration:
return this.addSegment(n, r, this.schema.variableColor);
case t.SyntaxKind.PropertyAccessExpression:
return e.parent.getChildAt(0).getText() === e.getText() ? this.addSegment(n, r, this.schema.variableColor) : this.addSegment(n, r, this.schema.propertyAccessExpressionColor);
case t.SyntaxKind.ExpressionWithTypeArguments:
return this.addSegment(n, r, this.schema.expressionWithTypeArgumentsColor);
case t.SyntaxKind.BreakStatement:
case t.SyntaxKind.ShorthandPropertyAssignment:
case t.SyntaxKind.BindingElement:
return this.addSegment(n, r, this.schema.variableColor);
case t.SyntaxKind.BinaryExpression:
case t.SyntaxKind.SwitchStatement:
case t.SyntaxKind.TemplateSpan:
return this.addSegment(n, r, this.schema.variableColor);
case t.SyntaxKind.TypeReference:
case t.SyntaxKind.TypeAliasDeclaration:
return this.addSegment(n, r, this.schema.typeColor);
case t.SyntaxKind.NewExpression:
return this.addSegment(n, r, this.schema.variableColor);
}
}
/**
* Processes a TypeScript template expression and adds highlighting segments for its literal parts.
*
* @param templateExpression - The TypeScript template expression to be processed
*
* @remarks
* This method adds color segments for both the template head and each template span's literal part.
* All template string components are highlighted using the color defined in the schema's stringColor.
*
* @example
* ```ts
* // Given a template expression like: `Hello ${name}`
* this.processTemplateExpression(templateNode);
* // Both "Hello " and the closing part after the expression will be highlighted
* ```
*
* @see addSegment
*
* @since 1.0.0
*/
processTemplateExpression(e) {
let r = e.head.getStart(), n = e.head.getEnd();
this.addSegment(r, n, this.schema.stringColor), e.templateSpans.forEach((o) => {
let s = o.literal.getStart(), a = o.literal.getEnd();
this.addSegment(s, a, this.schema.stringColor);
});
}
/**
* Processes a TypeScript AST node and adds highlighting segments based on the node's kind.
*
* @param node - The TypeScript AST node to be processed
*
* @remarks
* This method identifies the node's kind and applies the appropriate color for highlighting.
* It handles various syntax kinds, including literals (string, numeric, regular expressions),
* template expressions, identifiers, and type references.
* For complex node types like template expressions and identifiers, it delegates to specialized processing methods.
*
* @throws Error - When casting to TypeParameterDeclaration fails for non-compatible node kinds
*
* @example
* ```ts
* // Inside the CodeHighlighter class
* const node = sourceFile.getChildAt(0);
* this.processNode(node);
* // Node is now added to the segment map with appropriate colors
* ```
*
* @see processTemplateExpression
* @see processIdentifier
*
* @since 1.0.0
*/
processNode(e) {
let r = e.getStart(), n = e.getEnd();
switch (e.kind) {
case t.SyntaxKind.TypeParameter:
return this.addSegment(r, r + e.name.text.length, this.schema.typeColor);
case t.SyntaxKind.TypeReference:
return this.addSegment(r, n, this.schema.typeColor);
case t.SyntaxKind.StringLiteral:
case t.SyntaxKind.NoSubstitutionTemplateLiteral:
return this.addSegment(r, n, this.schema.stringColor);
case t.SyntaxKind.RegularExpressionLiteral:
return this.addSegment(r, n, this.schema.regularExpressionColor);
case t.SyntaxKind.TemplateExpression:
return this.processTemplateExpression(e);
case t.SyntaxKind.Identifier:
return this.processIdentifier(e);
case t.SyntaxKind.BigIntLiteral:
case t.SyntaxKind.NumericLiteral:
return this.addSegment(r, n, this.schema.numericLiteralColor);
}
}
};
function y(h, e = {}) {
let r = t.createSourceFile("temp.ts", h, t.ScriptTarget.Latest, !0, t.ScriptKind.TS), n = new l(r, h, Object.assign(m, e));
function o(s) {
n.parseNode(s);
for (let a = 0; a < s.getChildCount(); a++)
o(s.getChildAt(a));
}
return t.forEachChild(r, o), n.highlight();
}
export {
l as CodeHighlighter,
y as highlightCode
};
//# sourceMappingURL=highlighter.component.js.map