1/*
2 * Copyright (c) 2023-2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16import * as ts from 'typescript';
17import type { ProblemInfo } from '../ProblemInfo';
18import { ProblemSeverity } from '../ProblemSeverity';
19import { getStrictDiagnostics } from './TypeScriptDiagnosticsExtractor';
20import { FaultID } from '../Problems';
21import { faultsAttrs } from '../FaultAttrs';
22
23export interface TSCCompiledProgram {
24  getProgram: () => ts.Program;
25  getStrictDiagnostics: (fileName: string) => ts.Diagnostic[];
26}
27
28export class TSCCompiledProgramSimple implements TSCCompiledProgram {
29  private readonly program: ts.Program;
30
31  constructor(program: ts.Program) {
32    this.program = program;
33  }
34
35  getProgram(): ts.Program {
36    return this.program;
37  }
38
39  getStrictDiagnostics(fileName: string): ts.Diagnostic[] {
40    void this;
41    void fileName;
42    return [];
43  }
44}
45
46export type ProgressCallback = (message: string) => void;
47
48export class TSCCompiledProgramWithDiagnostics implements TSCCompiledProgram {
49  private readonly program: ts.Program;
50  private readonly cachedDiagnostics: Map<string, ts.Diagnostic[]> = new Map();
51
52  constructor(
53    strict: ts.Program,
54    nonStrict: ts.Program,
55    inputFiles: string[],
56    cancellationToken?: ts.CancellationToken,
57    progressCb?: ProgressCallback
58  ) {
59    this.program = strict;
60
61    inputFiles.forEach((fileName) => {
62      progressCb?.(fileName);
63
64      const sourceFile = this.program.getSourceFile(fileName);
65      if (sourceFile !== undefined) {
66        this.cachedDiagnostics.set(
67          sourceFile.fileName,
68          getStrictDiagnostics(strict, nonStrict, sourceFile.fileName, cancellationToken)
69        );
70      }
71    });
72  }
73
74  getProgram(): ts.Program {
75    return this.program;
76  }
77
78  getStrictDiagnostics(fileName: string): ts.Diagnostic[] {
79    return this.cachedDiagnostics.get(fileName) ?? [];
80  }
81}
82
83export function transformDiagnostic(diagnostic: ts.Diagnostic): ProblemInfo {
84  const startPos = diagnostic.start!;
85  const start = getLineAndColumn(diagnostic.file!, startPos);
86  const endPos = startPos + diagnostic.length!;
87  const end = getLineAndColumn(diagnostic.file!, endPos);
88  const messageText = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
89  const faultId = FaultID.StrictDiagnostic;
90
91  return {
92    line: start.line,
93    column: start.column,
94    endLine: end.line,
95    endColumn: end.column,
96    start: startPos,
97    end: endPos,
98    type: 'StrictModeError',
99    // expect strict options to always present
100    severity: ProblemSeverity.ERROR,
101    problem: FaultID[faultId],
102    suggest: messageText,
103    rule: messageText,
104    ruleTag: faultsAttrs[faultId] ? faultsAttrs[faultId].cookBookRef : 0
105  };
106}
107
108/**
109 * Returns line and column of the position, counts from 1
110 */
111function getLineAndColumn(file: ts.SourceFile, position: number): { line: number; column: number } {
112  const { line, character } = file.getLineAndCharacterOfPosition(position);
113  // TSC counts lines and columns from zero
114  return {
115    line: line + 1,
116    column: character + 1
117  };
118}
119