1/*
2 * Copyright (c) 2023 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 fs from 'fs';
17import path from 'path';
18import * as ts from 'typescript';
19import {
20  globalProgram,
21  projectConfig
22} from '../main';
23import {
24  toUnixPath,
25  getRollupCache,
26  setRollupCache
27} from './utils';
28import {
29  resolveModuleNames,
30  resolveTypeReferenceDirectives,
31  fileHashScriptVersion,
32  LanguageServiceCache,
33} from './ets_checker';
34import { ARKTS_LINTER_BUILD_INFO_SUFFIX } from './pre_define';
35
36const arkTSDir: string = 'ArkTS';
37const arkTSLinterOutputFileName: string = 'ArkTSLinter_output.json';
38const spaceNumBeforeJsonLine = 2;
39
40interface OutputInfo {
41  categoryInfo: string | undefined;
42  fileName: string | undefined;
43  line: number | undefined;
44  character: number | undefined;
45  messageText: string | ts.DiagnosticMessageChain;
46}
47
48export enum ArkTSLinterMode {
49  NOT_USE = 0,
50  COMPATIBLE_MODE = 1,
51  STANDARD_MODE = 2
52}
53
54export enum ArkTSVersion {
55  ArkTS_1_0,
56  ArkTS_1_1,
57}
58
59export type ProcessDiagnosticsFunc = (diagnostics: ts.Diagnostic) => void;
60
61function getArkTSVersionString(arkTSVersion: ArkTSVersion): string {
62  return arkTSVersion === ArkTSVersion.ArkTS_1_0 ? 'ArkTS_1_0' : 'ArkTS_1_1';
63}
64
65export function doArkTSLinter(arkTSVersion: ArkTSVersion, arkTSMode: ArkTSLinterMode,
66  builderProgram: ts.BuilderProgram, printDiagnostic: ProcessDiagnosticsFunc, shouldWriteFile: boolean = true,
67  buildInfoWriteFile?: ts.WriteFileCallback): ts.Diagnostic[] {
68  if (arkTSMode === ArkTSLinterMode.NOT_USE) {
69    return [];
70  }
71
72  let diagnostics: ts.Diagnostic[] = [];
73
74  if (arkTSVersion === ArkTSVersion.ArkTS_1_0) {
75    diagnostics = ts.ArkTSLinter_1_0.runArkTSLinter(builderProgram, /*srcFile*/ undefined, buildInfoWriteFile,
76      getArkTSVersionString(arkTSVersion));
77  } else {
78    diagnostics = ts.ArkTSLinter_1_1.runArkTSLinter(builderProgram, /*srcFile*/ undefined, buildInfoWriteFile,
79      getArkTSVersionString(arkTSVersion));
80  }
81
82  removeOutputFile();
83  if (diagnostics.length === 0) {
84    return [];
85  }
86
87  if (arkTSMode === ArkTSLinterMode.COMPATIBLE_MODE) {
88    processArkTSLinterReportAsWarning(diagnostics, printDiagnostic, shouldWriteFile);
89  } else {
90    processArkTSLinterReportAsError(diagnostics, printDiagnostic);
91  }
92
93  return diagnostics;
94}
95
96function processArkTSLinterReportAsError(diagnostics: ts.Diagnostic[], printDiagnostic: ProcessDiagnosticsFunc): void {
97  diagnostics.forEach((diagnostic: ts.Diagnostic) => {
98    printDiagnostic(diagnostic);
99  });
100  printArkTSLinterFAQ(diagnostics, printDiagnostic);
101}
102
103function processArkTSLinterReportAsWarning(diagnostics: ts.Diagnostic[], printDiagnostic: ProcessDiagnosticsFunc,
104  shouldWriteFile: boolean): void {
105  const filePath = shouldWriteFile ? writeOutputFile(diagnostics) : undefined;
106  if (filePath === undefined) {
107    diagnostics.forEach((diagnostic: ts.Diagnostic) => {
108      const originalCategory = diagnostic.category;
109      diagnostic.category = ts.DiagnosticCategory.Warning;
110      printDiagnostic(diagnostic);
111      diagnostic.category = originalCategory;
112    });
113    printArkTSLinterFAQ(diagnostics, printDiagnostic);
114    return;
115  }
116  const logMessage = `Has ${diagnostics.length} ArkTS Linter Error. You can get the output in ${filePath}`;
117  const arkTSDiagnostic: ts.Diagnostic = {
118    file: undefined,
119    start: undefined,
120    length: undefined,
121    messageText: logMessage,
122    category: ts.DiagnosticCategory.Warning,
123    code: -1,
124    reportsUnnecessary: undefined,
125    reportsDeprecated: undefined
126  };
127  printDiagnostic(arkTSDiagnostic);
128
129  printArkTSLinterFAQ(diagnostics, printDiagnostic);
130}
131
132function writeOutputFile(diagnostics: ts.Diagnostic[]): string | undefined {
133  let filePath: string = toUnixPath(projectConfig.cachePath);
134  if (!fs.existsSync(filePath)) {
135    return undefined;
136  }
137  filePath = toUnixPath(path.join(filePath, arkTSDir));
138  if (!fs.existsSync(filePath)) {
139    fs.mkdirSync(filePath);
140  }
141  filePath = toUnixPath((path.join(filePath, arkTSLinterOutputFileName)));
142  const outputInfo: OutputInfo[] = [];
143  diagnostics.forEach((diagnostic: ts.Diagnostic) => {
144    const { line, character }: ts.LineAndCharacter =
145      diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start!);
146    outputInfo.push({
147      categoryInfo: diagnostic.category === ts.DiagnosticCategory.Error ? 'Error' : 'Warning',
148      fileName: diagnostic.file?.fileName,
149      line: line + 1,
150      character: character + 1,
151      messageText: diagnostic.messageText
152    });
153  });
154  let output: string | undefined = filePath;
155  try {
156    fs.writeFileSync(filePath, JSON.stringify(outputInfo, undefined, spaceNumBeforeJsonLine));
157  } catch {
158    output = undefined;
159  }
160  return output;
161}
162
163function removeOutputFile(): void {
164  let filePath: string = toUnixPath(projectConfig.cachePath);
165  if (!fs.existsSync(filePath)) {
166    return;
167  }
168  filePath = toUnixPath(path.join(filePath, arkTSDir));
169  if (!fs.existsSync(filePath)) {
170    return;
171  }
172  filePath = toUnixPath((path.join(filePath, arkTSLinterOutputFileName)));
173  if (fs.existsSync(filePath)) {
174    fs.rmSync(filePath);
175  }
176}
177
178function printArkTSLinterFAQ(diagnostics: ts.Diagnostic[], printDiagnostic: ProcessDiagnosticsFunc): void {
179  if (diagnostics === undefined || diagnostics.length === undefined || diagnostics.length <= 0) {
180    return;
181  }
182
183  const logMessageFAQ = 'For details about ArkTS syntax errors, see FAQs';
184  const arkTSFAQDiagnostic: ts.Diagnostic = {
185    file: undefined,
186    start: undefined,
187    length: undefined,
188    messageText: logMessageFAQ,
189    category: ts.DiagnosticCategory.Warning,
190    code: -1,
191    reportsUnnecessary: undefined,
192    reportsDeprecated: undefined
193  };
194  printDiagnostic(arkTSFAQDiagnostic);
195}
196