13af6ab5fSopenharmony_ci/* 23af6ab5fSopenharmony_ci * Copyright (c) 2024 Huawei Device Co., Ltd. 33af6ab5fSopenharmony_ci * Licensed under the Apache License, Version 2.0 (the "License"); 43af6ab5fSopenharmony_ci * you may not use this file except in compliance with the License. 53af6ab5fSopenharmony_ci * You may obtain a copy of the License at 63af6ab5fSopenharmony_ci * 73af6ab5fSopenharmony_ci * http://www.apache.org/licenses/LICENSE-2.0 83af6ab5fSopenharmony_ci * 93af6ab5fSopenharmony_ci * Unless required by applicable law or agreed to in writing, software 103af6ab5fSopenharmony_ci * distributed under the License is distributed on an "AS IS" BASIS, 113af6ab5fSopenharmony_ci * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 123af6ab5fSopenharmony_ci * See the License for the specific language governing permissions and 133af6ab5fSopenharmony_ci * limitations under the License. 143af6ab5fSopenharmony_ci */ 153af6ab5fSopenharmony_ci 163af6ab5fSopenharmony_ciimport type { RawSourceMap } from 'typescript'; 173af6ab5fSopenharmony_ciimport { SourceMap } from 'magic-string'; 183af6ab5fSopenharmony_ciimport { SourceMapSegment, decode } from '@jridgewell/sourcemap-codec'; 193af6ab5fSopenharmony_ciimport assert from 'assert'; 203af6ab5fSopenharmony_ci 213af6ab5fSopenharmony_cienum SegmentIndex { 223af6ab5fSopenharmony_ci ORIGINAL_COLUMN_INDEX = 0, 233af6ab5fSopenharmony_ci SOURCE_INDEX = 1, 243af6ab5fSopenharmony_ci TRANSFORMED_LINE_INDEX = 2, 253af6ab5fSopenharmony_ci TRANSFORMED_COLUMN_INDEX = 3, 263af6ab5fSopenharmony_ci NAME_INDEX = 4, 273af6ab5fSopenharmony_ci} 283af6ab5fSopenharmony_ci 293af6ab5fSopenharmony_ci/** 303af6ab5fSopenharmony_ci * The sourcemap format with decoded mappings with number type. 313af6ab5fSopenharmony_ci */ 323af6ab5fSopenharmony_ciexport interface ExistingDecodedSourceMap { 333af6ab5fSopenharmony_ci file?: string; 343af6ab5fSopenharmony_ci mappings: SourceMapSegment[][]; 353af6ab5fSopenharmony_ci names?: string[]; 363af6ab5fSopenharmony_ci sourceRoot?: string; 373af6ab5fSopenharmony_ci sources: string[]; 383af6ab5fSopenharmony_ci sourcesContent?: string[]; 393af6ab5fSopenharmony_ci version: number; 403af6ab5fSopenharmony_ci} 413af6ab5fSopenharmony_ci 423af6ab5fSopenharmony_ciinterface BaseSource { 433af6ab5fSopenharmony_ci traceSegment(line: number, column: number, name: string): SourceMapSegmentObj | null; 443af6ab5fSopenharmony_ci} 453af6ab5fSopenharmony_ci 463af6ab5fSopenharmony_ci/** 473af6ab5fSopenharmony_ci * The source file info. 483af6ab5fSopenharmony_ci */ 493af6ab5fSopenharmony_ciexport class Source implements BaseSource { 503af6ab5fSopenharmony_ci readonly content: string | null; 513af6ab5fSopenharmony_ci readonly filename: string; 523af6ab5fSopenharmony_ci isOriginal = true; 533af6ab5fSopenharmony_ci 543af6ab5fSopenharmony_ci constructor(filename: string, content: string | null) { 553af6ab5fSopenharmony_ci this.filename = filename; 563af6ab5fSopenharmony_ci this.content = content; 573af6ab5fSopenharmony_ci } 583af6ab5fSopenharmony_ci 593af6ab5fSopenharmony_ci traceSegment(line: number, column: number, name: string): SourceMapSegmentObj { 603af6ab5fSopenharmony_ci return { column, line, name, source: this }; 613af6ab5fSopenharmony_ci } 623af6ab5fSopenharmony_ci} 633af6ab5fSopenharmony_ci 643af6ab5fSopenharmony_ci/** 653af6ab5fSopenharmony_ci * The interpreted sourcemap line and column info. 663af6ab5fSopenharmony_ci */ 673af6ab5fSopenharmony_ciexport interface SourceMapSegmentObj { 683af6ab5fSopenharmony_ci column: number; 693af6ab5fSopenharmony_ci line: number; 703af6ab5fSopenharmony_ci name: string; 713af6ab5fSopenharmony_ci source: Source; 723af6ab5fSopenharmony_ci} 733af6ab5fSopenharmony_ci 743af6ab5fSopenharmony_citype MappingsNameType = { mappings: readonly SourceMapSegment[][]; names?: readonly string[] }; 753af6ab5fSopenharmony_citype TracedMappingsType = { mappings: SourceMapSegment[][]; names: string[]; sources: string[] }; 763af6ab5fSopenharmony_ci 773af6ab5fSopenharmony_ci/** 783af6ab5fSopenharmony_ci * Provide api tools related to sourcemap. 793af6ab5fSopenharmony_ci */ 803af6ab5fSopenharmony_ciexport class SourceMapLink implements BaseSource { 813af6ab5fSopenharmony_ci readonly mappings: readonly SourceMapSegment[][]; 823af6ab5fSopenharmony_ci readonly names?: readonly string[]; 833af6ab5fSopenharmony_ci readonly sources: BaseSource[]; 843af6ab5fSopenharmony_ci 853af6ab5fSopenharmony_ci constructor(map: MappingsNameType, sources: BaseSource[]) { 863af6ab5fSopenharmony_ci this.sources = sources; 873af6ab5fSopenharmony_ci this.names = map.names; 883af6ab5fSopenharmony_ci this.mappings = map.mappings; 893af6ab5fSopenharmony_ci } 903af6ab5fSopenharmony_ci 913af6ab5fSopenharmony_ci traceMappings(): TracedMappingsType { 923af6ab5fSopenharmony_ci const tracedSources: string[] = []; 933af6ab5fSopenharmony_ci const sourceIndexMap = new Map<string, number>(); 943af6ab5fSopenharmony_ci const sourcesContent: (string | null)[] = []; 953af6ab5fSopenharmony_ci const tracednames: string[] = []; 963af6ab5fSopenharmony_ci const nameIndexMap = new Map<string, number>(); 973af6ab5fSopenharmony_ci 983af6ab5fSopenharmony_ci const mappings = []; 993af6ab5fSopenharmony_ci 1003af6ab5fSopenharmony_ci for (const line of this.mappings) { 1013af6ab5fSopenharmony_ci const tracedLine: SourceMapSegment[] = []; 1023af6ab5fSopenharmony_ci 1033af6ab5fSopenharmony_ci for (const segment of line) { 1043af6ab5fSopenharmony_ci if (segment.length === 1) { // The number of elements is insufficient. 1053af6ab5fSopenharmony_ci continue; 1063af6ab5fSopenharmony_ci } 1073af6ab5fSopenharmony_ci const source = this.sources[segment[SegmentIndex.SOURCE_INDEX]]; 1083af6ab5fSopenharmony_ci if (!source) { 1093af6ab5fSopenharmony_ci continue; 1103af6ab5fSopenharmony_ci } 1113af6ab5fSopenharmony_ci // segment[2] records the line number of the code before transform, segment[3] records the column number of the code before transform. 1123af6ab5fSopenharmony_ci // segment[4] records the name from the names array. 1133af6ab5fSopenharmony_ci assert(segment.length >= 4, 'The length of the mapping segment is incorrect.'); 1143af6ab5fSopenharmony_ci let line: number = segment[SegmentIndex.TRANSFORMED_LINE_INDEX]; 1153af6ab5fSopenharmony_ci let column: number = segment[SegmentIndex.TRANSFORMED_COLUMN_INDEX]; 1163af6ab5fSopenharmony_ci // If the length of the segment is 5, it will have name content. 1173af6ab5fSopenharmony_ci let name: string = segment.length === 5 ? this.names[segment[SegmentIndex.NAME_INDEX]] : ''; 1183af6ab5fSopenharmony_ci const traced = source.traceSegment(line, column, name); 1193af6ab5fSopenharmony_ci 1203af6ab5fSopenharmony_ci if (traced) { 1213af6ab5fSopenharmony_ci this.analyzeTracedSource(traced, tracedSources, sourceIndexMap, sourcesContent); 1223af6ab5fSopenharmony_ci let sourceIndex = sourceIndexMap.get(traced.source.filename); 1233af6ab5fSopenharmony_ci const targetSegment: SourceMapSegment = [segment[SegmentIndex.ORIGINAL_COLUMN_INDEX], sourceIndex, traced.line, traced.column]; 1243af6ab5fSopenharmony_ci this.recordTracedName(traced, tracednames, nameIndexMap, targetSegment); 1253af6ab5fSopenharmony_ci tracedLine.push(targetSegment); 1263af6ab5fSopenharmony_ci } 1273af6ab5fSopenharmony_ci } 1283af6ab5fSopenharmony_ci 1293af6ab5fSopenharmony_ci mappings.push(tracedLine); 1303af6ab5fSopenharmony_ci } 1313af6ab5fSopenharmony_ci 1323af6ab5fSopenharmony_ci return { mappings, names: tracednames, sources: tracedSources }; 1333af6ab5fSopenharmony_ci } 1343af6ab5fSopenharmony_ci 1353af6ab5fSopenharmony_ci analyzeTracedSource(traced: SourceMapSegmentObj, tracedSources: string[], sourceIndexMap: Map<string, number>, sourcesContent: (string | null)[]): void { 1363af6ab5fSopenharmony_ci const content = traced.source.content; 1373af6ab5fSopenharmony_ci const filename = traced.source.filename; 1383af6ab5fSopenharmony_ci // Get the source index from sourceIndexMap, which is the second element of sourcemap. 1393af6ab5fSopenharmony_ci let sourceIndex = sourceIndexMap.get(filename); 1403af6ab5fSopenharmony_ci if (sourceIndex === undefined) { 1413af6ab5fSopenharmony_ci sourceIndex = tracedSources.length; 1423af6ab5fSopenharmony_ci tracedSources.push(filename); 1433af6ab5fSopenharmony_ci sourceIndexMap.set(filename, sourceIndex); 1443af6ab5fSopenharmony_ci sourcesContent[sourceIndex] = content; 1453af6ab5fSopenharmony_ci } else if (sourcesContent[sourceIndex] == null) { // Update text when content is empty. 1463af6ab5fSopenharmony_ci sourcesContent[sourceIndex] = content; 1473af6ab5fSopenharmony_ci } else if (content != null && sourcesContent[sourceIndex] !== content) { 1483af6ab5fSopenharmony_ci throw new Error(`Multiple conflicting contents for sourcemap source: ${filename}`); 1493af6ab5fSopenharmony_ci } 1503af6ab5fSopenharmony_ci } 1513af6ab5fSopenharmony_ci 1523af6ab5fSopenharmony_ci recordTracedName(traced: SourceMapSegmentObj, tracednames: string[], nameIndexMap: Map<string, number>, targetSegment: SourceMapSegment): void { 1533af6ab5fSopenharmony_ci if (traced.name) { 1543af6ab5fSopenharmony_ci const name = traced.name; 1553af6ab5fSopenharmony_ci let nameIndex = nameIndexMap.get(name); 1563af6ab5fSopenharmony_ci if (nameIndex === undefined) { 1573af6ab5fSopenharmony_ci nameIndex = tracednames.length; 1583af6ab5fSopenharmony_ci tracednames.push(name); 1593af6ab5fSopenharmony_ci nameIndexMap.set(name, nameIndex); 1603af6ab5fSopenharmony_ci } 1613af6ab5fSopenharmony_ci // Add the fourth element: name position 1623af6ab5fSopenharmony_ci targetSegment.push(nameIndex); 1633af6ab5fSopenharmony_ci } 1643af6ab5fSopenharmony_ci } 1653af6ab5fSopenharmony_ci 1663af6ab5fSopenharmony_ci traceSegment(line: number, column: number, name: string): SourceMapSegmentObj | null { 1673af6ab5fSopenharmony_ci const segments = this.mappings[line]; 1683af6ab5fSopenharmony_ci if (!segments) { 1693af6ab5fSopenharmony_ci return null; 1703af6ab5fSopenharmony_ci } 1713af6ab5fSopenharmony_ci 1723af6ab5fSopenharmony_ci // Binary search segment for the target columns. 1733af6ab5fSopenharmony_ci let binarySearchStart = 0; 1743af6ab5fSopenharmony_ci let binarySearchEnd = segments.length - 1; // Get the last elemnt index. 1753af6ab5fSopenharmony_ci 1763af6ab5fSopenharmony_ci while (binarySearchStart <= binarySearchEnd) { 1773af6ab5fSopenharmony_ci // Calculate the intermediate index. 1783af6ab5fSopenharmony_ci const m = (binarySearchStart + binarySearchEnd) >> 1; 1793af6ab5fSopenharmony_ci const tempSegment = segments[m]; 1803af6ab5fSopenharmony_ci let tempColumn = tempSegment[SegmentIndex.ORIGINAL_COLUMN_INDEX]; 1813af6ab5fSopenharmony_ci // If a sourcemap does not have sufficient resolution to contain a necessary mapping, e.g. because it only contains line information, we 1823af6ab5fSopenharmony_ci // use the best approximation we could find 1833af6ab5fSopenharmony_ci if (tempColumn === column || binarySearchStart === binarySearchEnd) { 1843af6ab5fSopenharmony_ci if (tempSegment.length === 1) { // The number of elements is insufficient. 1853af6ab5fSopenharmony_ci return null; 1863af6ab5fSopenharmony_ci } 1873af6ab5fSopenharmony_ci const tracedSource = tempSegment[SegmentIndex.SOURCE_INDEX]; 1883af6ab5fSopenharmony_ci const source = this.sources[tracedSource]; 1893af6ab5fSopenharmony_ci if (!source) { 1903af6ab5fSopenharmony_ci return null; 1913af6ab5fSopenharmony_ci } 1923af6ab5fSopenharmony_ci 1933af6ab5fSopenharmony_ci let tracedLine: number = tempSegment[SegmentIndex.TRANSFORMED_LINE_INDEX]; 1943af6ab5fSopenharmony_ci let tracedColumn: number = tempSegment[SegmentIndex.TRANSFORMED_COLUMN_INDEX]; 1953af6ab5fSopenharmony_ci let tracedName: string = tempSegment.length === 5 ? this.names[tempSegment[SegmentIndex.NAME_INDEX]] : name; 1963af6ab5fSopenharmony_ci return source.traceSegment(tracedLine, tracedColumn, tracedName); 1973af6ab5fSopenharmony_ci } 1983af6ab5fSopenharmony_ci if (tempColumn > column) { 1993af6ab5fSopenharmony_ci // Target is in the left half 2003af6ab5fSopenharmony_ci binarySearchEnd = m - 1; 2013af6ab5fSopenharmony_ci } else { 2023af6ab5fSopenharmony_ci // Target is in the right half 2033af6ab5fSopenharmony_ci binarySearchStart = m + 1; 2043af6ab5fSopenharmony_ci } 2053af6ab5fSopenharmony_ci } 2063af6ab5fSopenharmony_ci 2073af6ab5fSopenharmony_ci return null; 2083af6ab5fSopenharmony_ci } 2093af6ab5fSopenharmony_ci} 2103af6ab5fSopenharmony_ci 2113af6ab5fSopenharmony_ci/** 2123af6ab5fSopenharmony_ci * Decode the sourcemap from string format to number format. 2133af6ab5fSopenharmony_ci * @param map The sourcemap with raw string format, eg. mappings: IAGS,OAAO,GAAE,MAAM,CAAA; 2143af6ab5fSopenharmony_ci * @returns The sourcemap with decoded number format, eg. mappings: [4,0,3,9], [7,0,0,7], [3,0,0,2], [6,0,0,6], [1,0,0,0] 2153af6ab5fSopenharmony_ci */ 2163af6ab5fSopenharmony_ciexport function decodeSourcemap(map: RawSourceMap): ExistingDecodedSourceMap | null { 2173af6ab5fSopenharmony_ci if (!map) { 2183af6ab5fSopenharmony_ci return null; 2193af6ab5fSopenharmony_ci } 2203af6ab5fSopenharmony_ci if (map.mappings === '') { 2213af6ab5fSopenharmony_ci return { mappings: [], names: [], sources: [], version: 3 }; // 3 is the sourcemap version. 2223af6ab5fSopenharmony_ci } 2233af6ab5fSopenharmony_ci const mappings: SourceMapSegment[][] = decode(map.mappings); 2243af6ab5fSopenharmony_ci return { ...map, mappings: mappings }; 2253af6ab5fSopenharmony_ci} 2263af6ab5fSopenharmony_ci 2273af6ab5fSopenharmony_cifunction generateChain(sourcemapChain: ExistingDecodedSourceMap[], map: RawSourceMap): void { 2283af6ab5fSopenharmony_ci sourcemapChain.push(decodeSourcemap(map)); 2293af6ab5fSopenharmony_ci} 2303af6ab5fSopenharmony_ci 2313af6ab5fSopenharmony_ci/** 2323af6ab5fSopenharmony_ci * Merge the sourcemaps of the two processes into the sourcemap of the complete process. 2333af6ab5fSopenharmony_ci * @param previousMap The sourcemap before obfuscation process, such as ets-loader transform 2343af6ab5fSopenharmony_ci * @param currentMap The sourcemap of obfuscation process 2353af6ab5fSopenharmony_ci * @returns The merged sourcemap 2363af6ab5fSopenharmony_ci */ 2373af6ab5fSopenharmony_ciexport function mergeSourceMap(previousMap: RawSourceMap, currentMap: RawSourceMap): RawSourceMap { 2383af6ab5fSopenharmony_ci const sourcemapChain: ExistingDecodedSourceMap[] = []; 2393af6ab5fSopenharmony_ci // The ets-loader esmodule mode processes one file at a time, so get the file name at index 1 2403af6ab5fSopenharmony_ci const sourceFileName = previousMap.sources.length === 1 ? previousMap.sources[0] : ''; 2413af6ab5fSopenharmony_ci const source: Source = new Source(sourceFileName, null); 2423af6ab5fSopenharmony_ci generateChain(sourcemapChain, previousMap); 2433af6ab5fSopenharmony_ci generateChain(sourcemapChain, currentMap); 2443af6ab5fSopenharmony_ci const collapsedSourcemap: SourceMapLink = sourcemapChain.reduce( 2453af6ab5fSopenharmony_ci (source: BaseSource, map: ExistingDecodedSourceMap): SourceMapLink => { 2463af6ab5fSopenharmony_ci return new SourceMapLink(map, [source]); 2473af6ab5fSopenharmony_ci }, 2483af6ab5fSopenharmony_ci source, 2493af6ab5fSopenharmony_ci ) as SourceMapLink; 2503af6ab5fSopenharmony_ci const tracedMappings: TracedMappingsType = collapsedSourcemap.traceMappings(); 2513af6ab5fSopenharmony_ci const result: RawSourceMap = new SourceMap({ ...tracedMappings, file: previousMap.file }) as RawSourceMap; 2523af6ab5fSopenharmony_ci result.sourceRoot = previousMap.sourceRoot; 2533af6ab5fSopenharmony_ci return result; 2543af6ab5fSopenharmony_ci} 255