UNPKG

@remotex-labs/xmap

Version:

A library with a sourcemap parser and TypeScript code formatter for the CLI

400 lines (399 loc) 15.1 kB
// 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