UNPKG

@remotex-labs/xmap

Version:

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

867 lines (866 loc) 30.8 kB
/** * Represents a source map structure used for mapping code within a file to its original source * @since 1.0.0 */ export interface SourceMapInterface { /** * The generated file's name that the source map is associated with * @since 1.0.0 */ file?: string | null; /** * An array of variable/function names present in the original source * @since 1.0.0 */ names: Array<string>; /** * The version of the source map specification (standard is 3) * @since 1.0.0 */ version: number; /** * An array of URLs or paths to the original source files * @since 1.0.0 */ sources: Array<string>; /** * VLQ encoded string that maps generated code back to original source code * @since 1.0.0 */ mappings: string; /** * Root URL for resolving the sources * @since 1.0.0 */ sourceRoot?: string | null; /** * Array containing the content of the original source files * @since 1.0.0 */ sourcesContent?: Array<string>; } /** * Represents a position in source code with mapping information * @since 1.0.0 */ export interface PositionInterface { /** * Name of the identifier at this position * @since 1.0.0 */ name: string | null; /** * Line number in the original source * @since 1.0.0 */ line: number; /** * Column number in the original source * @since 1.0.0 */ column: number; /** * Path or URL to the original source file * @since 1.0.0 */ source: string; /** * Root URL for resolving the source * @since 1.0.0 */ sourceRoot: string | null; /** * Index of the source in the sources array * @since 1.0.0 */ sourceIndex: number; /** * Line number in the generated code * @since 1.0.0 */ generatedLine: number; /** * Column number in the generated code * @since 1.0.0 */ generatedColumn: number; } /** * Position in source code including the original source content * * @see PositionInterface * @since 1.0.0 */ export interface PositionWithContentInterface extends PositionInterface { /** * Content of the original source file * @since 1.0.0 */ sourcesContent: string; } /** * Position in source code including code fragment information * * @see PositionInterface * @since 1.0.0 */ export interface PositionWithCodeInterface extends PositionInterface { /** * Code fragment from the original source * @since 1.0.0 */ code: string; /** * Ending line number of the code fragment * @since 1.0.0 */ endLine: number; /** * Starting line number of the code fragment * @since 1.0.0 */ startLine: number; } /** * Options for retrieving source code context * @since 1.0.0 */ export interface SourceOptionsInterface { /** * Number of lines to include after the target line * @since 1.0.0 */ linesAfter?: number; /** * Number of lines to include before the target line * @since 1.0.0 */ linesBefore?: number; } /** * Encodes a given number using Variable-Length Quantity (VLQ) encoding. Negative numbers are encoded by * converting to a non-negative representation and the continuation bit is used to indicate if more bytes follow. * * @param value - The number to be encoded * @returns The VLQ encoded string represented as Base64 characters * * @throws Error - If the value cannot be properly encoded * * @remarks The encoding process shifts the value left by 1 bit to accommodate a sign bit in the least significant position. * Negative values have their sign bit set to 1, while positive values have it set to 0. * * @example * ```ts * // Encoding a positive number * const encoded = encodeVLQ(25); // Returns "Y" * * // Encoding a negative number * const encodedNegative = encodeVLQ(-25); // Returns "Z" * ``` * * @see decodeVLQ - For the corresponding decode function * * @since 1.0.0 */ export declare function encodeVLQ(value: number): string; /** * Encodes an array of numbers using VLQ encoding by individually encoding each number and concatenating the results. * * @param values - The array of numbers to be encoded * @returns The concatenated VLQ encoded string * * @example * ```ts * // Encoding multiple values * const encoded = encodeArrayVLQ([1, 0, -5]); // Returns "CAAK" * ``` * * @see encodeVLQ - The underlying function used to encode each number * * @since 1.0.0 */ export declare function encodeArrayVLQ(values: number[]): string; /** * Decodes a VLQ encoded string back into an array of numbers by processing Base64 characters and continuation bits. * * @param data - The VLQ encoded string * @returns The array of decoded numbers * * @throws Error - If the string contains invalid Base64 characters * * @remarks The decoding process examines each Base64 character, * checking for continuation bits and progressively building up numeric values. * The sign bit is extracted from the least significant position * to determine if the original number was negative. * * @example * ```ts * // Decoding a simple VLQ string * const decoded = decodeVLQ("Y"); // Returns [25] * * // Decoding multiple values * const multiDecoded = decodeVLQ("CAAK"); // Returns [1, 0, -5] * ``` * * @see encodeVLQ - For the corresponding encode function * * @since 1.0.0 */ export declare function decodeVLQ(data: string): number[]; /** * Represents a source map mapping segment that links positions between original and generated code. * * @interface SegmentInterface * * @property line - Original line number in the source file * @property column - Original column number in the source file * @property nameIndex - Index of the symbol name in the source map's names array (null if no associated name) * @property sourceIndex - Index of the source file in the source map's sources array * @property generatedLine - Line number in the generated output code * @property generatedColumn - Column number in the generated output code * * @remarks * These segments form the core data structure of source maps, providing the necessary * information to trace locations between original source code and generated output code. * Each segment represents a single mapping point in the transformation process. * * @example * ```ts * const segment: SegmentInterface = { * line: 42, * column: 10, * nameIndex: 5, * sourceIndex: 0, * generatedLine: 100, * generatedColumn: 15 * }; * ``` * * @since 1.0.0 */ export interface SegmentInterface { line: number; column: number; nameIndex: number | null; sourceIndex: number; generatedLine: number; generatedColumn: number; } /** * A specialized segment interface used during source map offset calculations. * * @property nameIndex - Index of the symbol name in the source map's names array (always numeric) * * @remarks * Unlike the base SegmentInterface where nameIndex can be null, * in offset calculations this value must always be a concrete numeric index. * This specialized interface ensures type safety during mapping offset operations. * * @example * ```ts * const offsetSegment: SegmentOffsetInterface = { * line: 42, * column: 10, * nameIndex: 5, // Must be a number, not null * sourceIndex: 0, * generatedLine: 100, * generatedColumn: 15 * }; * ``` * * @see SegmentInterface * * @since 1.0.0 */ export interface SegmentOffsetInterface extends SegmentInterface { nameIndex: number; } /** * Determines the matching behavior when searching for segments in a source map. * * @property BOUND - No directional preference; returns the first matching segment found * @property LOWER_BOUND - Prefers segments with column values lower than or equal to the target * @property UPPER_BOUND - Prefers segments with column values greater than or equal to the target * * @remarks * The bias affects how segment lookups behave when an exact position match cannot be found. * This provides flexibility in determining which nearby mapping should be preferred when * working with source map data that might not have exact matches for every position. * * @example * ```ts * // When searching for a position not exactly in the map: * findSegmentForPosition(line, column, Bias.LOWER_BOUND); // Prefers the position just before * findSegmentForPosition(line, column, Bias.UPPER_BOUND); // Prefers the position just after * ``` * * @since 1.0.0 */ export declare const enum Bias { BOUND = 0, LOWER_BOUND = 1, UPPER_BOUND = 2 } /** * Represents a collection of mapping segments for a single line in generated code. * * @remarks * A frame contains all the segments that map to a particular line of generated code. * Each segment within the frame provides mapping information for a specific range * of columns within that line. * * @example * ```ts * const lineFrame: FrameType = [ * { line: 10, column: 0, nameIndex: null, sourceIndex: 0, generatedLine: 5, generatedColumn: 0 }, * { line: 10, column: 5, nameIndex: 3, sourceIndex: 0, generatedLine: 5, generatedColumn: 10 } * ]; * ``` * * @see SegmentInterface * * @since 1.0.0 */ export type FrameType = Array<SegmentInterface>; /** * Represents the complete mapping structure of a source map. * * @remarks * A source map contains an array where each index corresponds to a line in the generated code. * Each entry is either: * - A frame (array of segments) containing mappings for that line * - Null, indicating the line has no mappings (represented by a semicolon in the source map) * * The array index corresponds directly to the line number in generated code (0-based). * * @example * ```ts * const sourceMap: MapType = [ * [{ line: 1, column: 0, nameIndex: null, sourceIndex: 0, generatedLine: 0, generatedColumn: 0 }], // Line 0 * null, // Line 1 has no mappings * [{ line: 2, column: 0, nameIndex: 1, sourceIndex: 0, generatedLine: 2, generatedColumn: 0 }] // Line 2 * ]; * ``` * * @see FrameType * @see SegmentInterface * * @since 1.0.0 */ export type MapType = Array<null | FrameType>; /** * Provides functionality for encoding and decoding source map mappings. * * The MappingProvider class handles the conversion between various mapping representations: * - String format (VLQ-encoded mappings) * - Structured array format (MapType) * - Internal structured representation * * It also provides methods to query and retrieve source map segments based on * generated or original source positions. * * @example * ```ts * // Create from VLQ-encoded mapping string * const provider = new MappingProvider(mappingString); * * // Get a segment by generated position * const segment = provider.getSegment(10, 15); * * // Convert back to mapping string * const encoded = provider.encode(); * ``` * * @since 1.0.0 */ export declare class MappingProvider { private mapping; /** * Creates a new MappingProvider instance. * * @param mapping - Source map mapping data in one of three formats: * - VLQ-encoded string * - Structured array (MapType) * - Another MappingProvider instance (copy constructor) * @param namesOffset - Optional offset to apply to name indices (default: 0) * @param sourceOffset - Optional offset to apply to source indices (default: 0) * * @remarks * The constructor automatically detects the mapping format and decodes it accordingly. * When providing offsets, these values will be added to the corresponding indices * in the decoded mapping data, which is useful when concatenating multiple source maps. * * @since 1.0.0 */ constructor(mapping: string, namesOffset?: number, sourceOffset?: number); constructor(mapping: MapType, namesOffset?: number, sourceOffset?: number); constructor(mapping: MappingProvider, namesOffset?: number, sourceOffset?: number); /** * Encodes the internal mapping representation to a VLQ-encoded mapping string. * * @returns VLQ-encoded mapping string compatible with the source map format specification * * @remarks * This method converts the internal structured mapping representation into a compact * string format using Variable Length Quantity (VLQ) encoding. * The resulting string follows the source map v3 format for the 'mappings' field. * * @see https://sourcemaps.info/spec.html * * @since 1.0.0 */ encode(): string; /** * Decodes mapping data into the internal representation. * * @param mapping - Mapping data to decode in one of three formats: * - VLQ-encoded string * - Structured array (MapType) * - Another MappingProvider instance * @param namesOffset - Optional offset for name indices (default: 0) * @param sourcesOffset - Optional offset for source indices (default: 0) * * @remarks * This method replaces the current internal mapping data with the newly decoded mapping. * The format of the input mapping is automatically detected and processed accordingly. * * @see MapType * @see MappingProvider * * @since 1.0.0 */ decode(mapping: MappingProvider | MapType | string, namesOffset?: number, sourcesOffset?: number): void; /** * Retrieves a segment based on a position in the generated code. * * @param generatedLine - Line number in generated code (1-based) * @param generatedColumn - Column number in generated code (0-based) * @param bias - Controls matching behavior when exact position not found: * - BOUND: No preference (default) * - LOWER_BOUND: Prefer segment with lower column * - UPPER_BOUND: Prefer segment with higher column * @returns Matching segment or null if not found * * @remarks * Uses binary search to efficiently locate matching segments. * When no exact match is found, the bias parameter determines which nearby segment to return. * * @since 1.0.0 */ getSegment(generatedLine: number, generatedColumn: number, bias?: Bias): SegmentInterface | null; /** * Retrieves a segment based on a position in the original source code. * * @param line - Line number in original source (1-based) * @param column - Column number in original source (0-based) * @param sourceIndex - Index of source file in the sources array * @param bias - Controls matching behavior when exact position not found: * - BOUND: No preference (default) * - LOWER_BOUND: Prefer segment with lower column * - UPPER_BOUND: Prefer segment with higher column * @returns Matching segment or null if not found * * @remarks * Searches across all mapping segments to find those matching the specified original source position. * When multiple matches are possible, the bias * parameter determines which segment to return. * * This operation is more expensive than getSegment as it must potentially * scan the entire mapping structure. * * @since 1.0.0 */ getOriginalSegment(line: number, column: number, sourceIndex: number, bias?: Bias): SegmentInterface | null; /** * Initializes a new segment offset object with default values. * * @param namesOffset - Initial name index offset value (default: 0) * @param sourceIndex - Initial source index offset value (default: 0) * @returns A new segment offset object with initialized position tracking values * * @remarks * This method creates an object that tracks position data during mapping operations. * All position values (line, column, generatedLine, generatedColumn) are initialized to 0, * while the nameIndex and sourceIndex can be initialized with custom offsets. * * @since 1.0.0 */ private initPositionOffsets; /** * Validates the format of an encoded mapping string. * * @param encodedSourceMap - The encoded source map string to validate * @returns `true` if the string contains only valid VLQ mapping characters, otherwise `false` * * @remarks * Checks if the string contains only characters valid in source map mappings: * - Base64 characters (a-z, A-Z, 0-9, +, /) * - Separators (commas for segments, semicolons for lines) * * This is a basic format validation and doesn't verify the semantic correctness * of the VLQ encoding itself. * * @since 1.0.0 */ private validateMappingString; /** * Validates that a segment's properties conform to expected types. * * @param segment - The segment object to validate * * @remarks * Performs the following validations on the segment properties: * - line: Must be a finite number * - column: Must be a finite number * - nameIndex: Must be either null or a finite number * - sourceIndex: Must be a finite number * - generatedLine: Must be a finite number * - generatedColumn: Must be a finite number * * This validation ensures that segments can be safely used in mapping operations * and prevents potential issues with non-numeric or infinite values. * * @throws Error - When any property of the segment is invalid, with a message * indicating which property failed validation and its value * * @since 1.0.0 */ private validateSegment; /** * Encodes a segment into a VLQ-encoded string based on relative offsets. * * @param segmentOffset - The current segment offset tracking state * @param segmentObject - The segment to encode * @returns A VLQ-encoded string representation of the segment * * @remarks * The encoding process: * 1. Adjusts line and column values (subtracts 1 to convert from 1-based to 0-based) * 2. Calculates relative differences between current values and previous offsets * 3. Creates an array with the following components: * - generatedColumn difference * - sourceIndex difference (0 if unchanged) * - line difference * - column difference * - nameIndex difference (only if nameIndex is present) * 4. Updates the segment offset state for the next encoding * 5. Returns the array as a VLQ-encoded string * * This method implements the source map V3 specification's delta encoding scheme * where values are stored as differences from previous positions. * * @since 1.0.0 */ private encodeSegment; /** * Encodes a mapping array into a VLQ-encoded mapping string following the source map V3 spec. * * @param map - The mapping array to encode, organized by generated lines and segments * @returns A complete VLQ-encoded mapping string with line and segment separators * * @remarks * The encoding process: * 1. Initializes position offsets to track state across the entire mapping * 2. Processes each frame (line) in the mapping array: * - Resets generated column offset to 0 at the start of each line * - Encodes each segment within the line using relative VLQ encoding * - Joins segments with commas (,) * 3. Joins lines with semicolons (;) * * Empty frames are preserved as empty strings in the output to maintain * the correct line numbering in the resulting source map. * * @since 1.0.0 */ private encodeMappings; /** * Converts a VLQ-decoded segment array into a structured segment object. * * @param segmentOffset - The current positional state tracking offsets * @param decodedSegment - Array of VLQ-decoded values representing relative offsets * @returns A complete segment object with absolute positions * * @remarks * The decoding process: * 1. Extracts position values from the decoded array: * - [0]: generatedColumn delta * - [1]: sourceIndex delta * - [2]: sourceLine delta * - [3]: sourceColumn delta * - [4]: nameIndex delta (optional) * 2. Updates the segmentOffset state by adding each delta * 3. Constructs a segment object with absolute positions (adding 1 to convert * from 0-based to 1-based coordinates) * 4. Handles nameIndex appropriately (null if not present in the input) * * This method implements the inverse operation of the delta encoding scheme * defined in the source map V3 specification. * * @since 1.0.0 */ private decodedSegment; /** * Decodes a VLQ-encoded mapping string into the internal mapping data structure. * * @param encodedMap - The VLQ-encoded mapping string from a source map * @param namesOffset - Base offset for name indices in the global names array * @param sourceOffset - Base offset for source indices in the global sources array * * @remarks * The decoding process: * 1. Validates the mapping string format before processing * 2. Splits the string into frames using semicolons (;) as line separators * 3. Initializes position offsets with the provided name and source offsets * 4. For each frame (line): * - Adds `null` to the mapping array if the frame is empty * - Resets the generated column offset to 0 for each new line * - Sets the generated line index using the offset + current index * - Splits segments using commas (,) and decodes each segment * - Transforms each decoded segment into a segment object * 5. Updates the internal mapping array with the decoded data * * Error handling includes validation checks and descriptive error messages * indicating which frame caused a decoding failure. * * @throws Error - When the mapping string format is invalid or decoding fails * * @since 1.0.0 */ private decodeMappingString; /** * Decodes a structured mapping array into the internal mapping representation. * * @param encodedMap - The structured mapping array (array of frames, with each frame being an array of segments) * @param namesOffset - Offset to add to each segment's nameIndex (for merging multiple source maps) * @param sourceOffset - Offset to add to each segment's sourceIndex (for merging multiple source maps) * * @remarks * The decoding process: * 1. Validates that the input is a properly structured array * 2. Tracks the current line offset based on the existing mapping length * 3. For each frame (line) in the mapping: * - Preserves null frames as-is (representing empty lines) * - Validates that each frame is an array * - For each segment in a frame: * - Validates the segment structure * - Applies the name and source offsets * - Adjusts the generated line index by the line offset * - Adds the processed frame to the internal mapping array * * This method is primarily used when combining multiple source maps or * importing mapping data from pre-structured arrays rather than VLQ strings. * The offsets enable proper indexing when concatenating multiple mappings. * * @throws Error - When the input format is invalid or segments don't conform to requirements * * @since 1.0.0 */ private decodeMappingArray; } /** * A service for validating and processing source maps that provides functionality for parsing, * position mapping, concatenation, and code snippet extraction. * * @param source - Source map data (SourceService, SourceMapInterface object, or JSON string) * @param file - Optional file name for the generated bundle * @returns A new SourceService instance * * @example * ```ts * const sourceMapJSON = '{"version": 3, "file": "bundle.js", "sources": ["foo.ts"], "names": [], "mappings": "AAAA"}'; * const sourceService = new SourceService(sourceMapJSON); * console.log(sourceService.file); // 'bundle.js' * ``` * * @since 1.0.0 */ export declare class SourceService { /** * The name of the generated file this source map applies to. * * @since 1.0.0 */ readonly file: string | null; /** * Provider for accessing and manipulating the base64 VLQ-encoded mappings. * * @since 1.0.0 */ readonly mappings: MappingProvider; /** * The root URL for resolving relative paths in the source files. * @since 1.0.0 */ readonly sourceRoot: string | null; /** * List of symbol names referenced by the mappings. * @since 1.0.0 */ readonly names: Array<string>; /** * Array of source file paths. * @since 1.0.0 */ readonly sources: Array<string>; /** * Array of source file contents. * @since 1.0.0 */ readonly sourcesContent: Array<string>; /** * Creates a new SourceService instance. * * @param source - Source map data (SourceService, SourceMapInterface object, or JSON string) * @param file - Optional file name for the generated bundle * * @throws Error - When a source map has an invalid format or missing required properties * * @since 1.0.0 */ constructor(source: SourceService); constructor(source: SourceMapInterface | string, file?: string | null); /** * Converts the source map data to a plain object. * * @returns A SourceMapInterface object representing the current state * * @example * ```ts * const mapObject = sourceService.getMapObject(); * console.log(mapObject.file); // 'bundle.js' * ``` * * @since 1.0.0 */ getMapObject(): SourceMapInterface; /** * Concatenates additional source maps into the current instance. * * @param maps - Source maps to concatenate with the current map * * @example * ```ts * sourceService.concat(anotherSourceMap); * console.log(sourceService.sources); // Updated source paths * ``` * * @throws Error - When no maps are provided * * @since 1.0.0 */ concat(...maps: Array<SourceMapInterface | SourceService>): void; /** * Creates a new SourceService instance with concatenated source maps. * * @param maps - Source maps to concatenate with a copy of the current map * @returns A new SourceService instance with the combined maps * * @example * ```ts * const newService = sourceService.concatNewMap(anotherSourceMap); * console.log(newService.sources); // Combined sources array * ``` * * @throws Error - When no maps are provided * * @since 1.0.0 */ concatNewMap(...maps: Array<SourceMapInterface | SourceService>): SourceService; /** * Finds position in generated code based on original source position. * * @param line - Line number in the original source * @param column - Column number in the original source * @param sourceIndex - Index or file path of the original source * @param bias - Position matching strategy (default: Bias.BOUND) * @returns Position information or null if not found * * @example * ```ts * const position = sourceService.getPositionByOriginal(1, 10, 'foo.ts'); * console.log(position?.generatedLine); // Line in generated code * ``` * * @since 1.0.0 */ getPositionByOriginal(line: number, column: number, sourceIndex: number | string, bias?: Bias): PositionInterface | null; /** * Finds position in an original source based on generated code position. * * @param line - Line number in the generated code * @param column - Column number in the generated code * @param bias - Position matching strategy (default: Bias.BOUND) * @returns Position information or null if not found * * @example * ```ts * const position = sourceService.getPosition(2, 15); * console.log(position?.source); // Original source file * ``` * * @since 1.0.0 */ getPosition(line: number, column: number, bias?: Bias): PositionInterface | null; /** * Retrieves position with source content for a location in generated code. * * @param line - Line number in the generated code * @param column - Column number in the generated code * @param bias - Position matching strategy (default: Bias.BOUND) * @returns Position with content information or null if not found * * @example * ```ts * const posWithContent = sourceService.getPositionWithContent(3, 5); * console.log(posWithContent?.sourcesContent); // Original source content * ``` * * @since 1.0.0 */ getPositionWithContent(line: number, column: number, bias?: Bias): PositionWithContentInterface | null; /** * Retrieves position with a code snippet from the original source. * * @param line - Line number in the generated code * @param column - Column number in the generated code * @param bias - Position matching strategy (default: Bias.BOUND) * @param options - Configuration for the amount of surrounding lines * @returns Position with code snippet or null if not found * * @example * ```ts * const posWithCode = sourceService.getPositionWithCode(4, 8, Bias.BOUND, { * linesBefore: 2, * linesAfter: 2 * }); * console.log(posWithCode?.code); // Code snippet with context * ``` * * @since 1.0.0 */ getPositionWithCode(line: number, column: number, bias?: Bias, options?: SourceOptionsInterface): PositionWithCodeInterface | null; /** * Serializes the source map to a JSON string. * * @returns JSON string representation of the source map * * @example * ```ts * const jsonString = sourceService.toString(); * console.log(jsonString); // '{"version":3,"file":"bundle.js",...}' * ``` * * @since 1.0.0 */ toString(): string; /** * Validates a source map object has all required properties. * * @param input - Source map object to validate * * @throws Error - When required properties are missing * * @since 1.0.0 */ private validateSourceMap; }