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