• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2021 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 ts from 'typescript';
17import fs from 'fs';
18import path from 'path';
19import { SourceMapGenerator } from 'source-map';
20
21import {
22  ReplaceResult,
23  sourceReplace,
24  validateUISyntax,
25  processSystemApi,
26  componentCollection
27} from './validate_ui_syntax';
28import {
29  LogType,
30  LogInfo,
31  emitLogInfo,
32  mkDir
33} from './utils';
34import {
35  BUILD_ON,
36  SUPERVISUAL,
37  SUPERVISUAL_SOURCEMAP_EXT
38} from './pre_define';
39import { projectConfig } from '../main.js';
40import { genETS } from '../codegen/codegen_ets.js';
41
42const visualMap: Map<number, number> = new Map();
43const slotMap: Map<number, number> = new Map();
44
45const red: string = '\u001b[31m';
46const reset: string = '\u001b[39m';
47
48function preProcess(source: string): string {
49  process.env.compiler = BUILD_ON;
50  if (/\.ets$/.test(this.resourcePath)) {
51    const result: ReplaceResult = sourceReplace(source, this.resourcePath);
52    let newContent: string = result.content;
53    const log: LogInfo[] = result.log.concat(validateUISyntax(source, newContent,
54      this.resourcePath, this.resourceQuery));
55    newContent = parseVisual(this.resourcePath, this.resourceQuery, newContent, log, source);
56    if (log.length) {
57      emitLogInfo(this, log);
58    }
59    return newContent;
60  } else {
61    return processSystemApi(source, false, this.resourcePath);
62  }
63}
64
65function parseVisual(resourcePath: string, resourceQuery: string, content: string,
66  log: LogInfo[], source: string): string {
67  if (!(componentCollection.entryComponent || componentCollection.customComponents) || !projectConfig.aceSuperVisualPath) {
68    return content;
69  }
70  const visualPath: string = findVisualFile(resourcePath);
71  if (!visualPath || !fs.existsSync(visualPath)) {
72    return content;
73  }
74  const visualContent: any = getVisualContent(visualPath, log);
75  if (!visualContent) {
76    return content;
77  }
78  visualMap.clear();
79  slotMap.clear();
80  const compilerOptions = ts.readConfigFile(
81    path.resolve(__dirname, '../tsconfig.json'), ts.sys.readFile).config.compilerOptions;
82  Object.assign(compilerOptions, {
83    'sourceMap': false
84  });
85  const sourceFile: ts.SourceFile = ts.createSourceFile(resourcePath, content,
86    ts.ScriptTarget.Latest, true, ts.ScriptKind.ETS, compilerOptions);
87  let newContent: string = content;
88  if (sourceFile.statements) {
89    sourceFile.statements.forEach(statement => {
90      newContent = parseStatement(statement, newContent, log, visualContent);
91    });
92  }
93  const result: ReplaceResult = sourceReplace(newContent, resourcePath);
94  newContent = result.content;
95  const resultLog: LogInfo[] = result.log.concat(validateUISyntax(source, newContent,
96    resourcePath, resourceQuery));
97  log.concat(resultLog);
98  if (!log.length) {
99    generateSourceMapForNewAndOriEtsFile(resourcePath, source);
100  }
101  return newContent;
102}
103
104function parseStatement(statement: ts.Statement, content: string, log: LogInfo[],
105  visualContent: any): string {
106  if (statement.kind === ts.SyntaxKind.StructDeclaration && statement.name) {
107    if (statement.members) {
108      statement.members.forEach(member => {
109        if (member.kind && member.kind === ts.SyntaxKind.MethodDeclaration) {
110          content = parseMember(statement, member, content, log, visualContent);
111        }
112      });
113    }
114  }
115  return content;
116}
117
118function parseMember(statement: ts.Statement, member: ts.MethodDeclaration, content: string,
119  log: LogInfo[], visualContent: any): string {
120  let newContent: string = content;
121  if (member.name && member.name.getText() === 'build') {
122    const buildBody: string = member.getText();
123    if (buildBody.replace(/\ +/g, '').replace(/[\r\n]/g, '') === 'build(){}') {
124      newContent = insertVisualCode(statement, member, visualContent, newContent);
125    } else {
126      log.push({
127        type: LogType.ERROR,
128        message: `when the corresponding visual file exists,` +
129          ` the build function of the entry component must be empty.`,
130        pos: member.pos
131      });
132    }
133  }
134  return newContent;
135}
136
137function insertVisualCode(statement: ts.Statement, member: ts.MethodDeclaration,
138  visualContent: any, content: string): string {
139  let newContent: string = content;
140  newContent = insertImport(visualContent, newContent);
141  newContent = insertVarAndFunc(member, visualContent, newContent, content);
142  newContent = insertBuild(member, visualContent, newContent, content);
143  newContent = insertAboutToAppear(statement, member, visualContent, newContent, content);
144  return newContent;
145}
146
147function insertImport(visualContent: any, content: string): string {
148  if (!visualContent.etsImport) {
149    return content;
150  }
151  const mediaQueryImport: string = visualContent.etsImport + '\n';
152  const newContent: string = mediaQueryImport + content;
153  slotMap.set(0, mediaQueryImport.length);
154  visualMap.set(0, mediaQueryImport.split('\n').length - 1);
155  return newContent;
156}
157
158function insertVarAndFunc(build: ts.MethodDeclaration, visualContent: any,
159  content: string, oriContent: string): string {
160  const visualVarAndFunc: string = (visualContent.etsVariable ? visualContent.etsVariable : '') +
161    (visualContent.etsFunction ? visualContent.etsFunction : '');
162  return visualVarAndFunc ? insertVisualCodeBeforePos(build, '\n' + visualVarAndFunc, content,
163    oriContent) : content;
164}
165
166function insertBuild(build: ts.MethodDeclaration, visualContent: any, content: string,
167  oriContent: string): string {
168  return visualContent.build ? insertVisualCodeAfterPos(build.body,
169    '\n' + visualContent.build + '\n', content, oriContent) : content;
170}
171
172function insertAboutToAppear(statement: ts.Statement, build: ts.MethodDeclaration,
173  visualContent: any, content: string, oriContent: string): string {
174  if (!visualContent.aboutToAppear) {
175    return content;
176  }
177  for (const member of statement.members) {
178    const hasAboutToAppear: boolean = member.kind && member.kind === ts.SyntaxKind.MethodDeclaration
179      && member.name && member.name.getText() === 'aboutToAppear';
180    if (hasAboutToAppear) {
181      return insertVisualCodeAfterPos(member.body, '\n' + visualContent.aboutToAppear, content,
182        oriContent);
183    }
184  }
185
186  const aboutToAppearFunc: string = '\n  aboutToAppear() {\n' + visualContent.aboutToAppear +
187    '  }\n';
188  return insertVisualCodeBeforePos(build, aboutToAppearFunc, content, oriContent);
189}
190
191function insertVisualCodeAfterPos(member: ts.Block, visualContent: string, content: string,
192  oriContent: string): string {
193  const contentBeforePos: string = oriContent.substring(0, member.getStart() + 1);
194  const originEtsFileLineNumber: number = contentBeforePos.split('\n').length;
195  const visualLines: number = visualContent.split('\n').length - 1;
196  const insertedLineNumbers: number = visualMap.get(originEtsFileLineNumber);
197  visualMap.set(originEtsFileLineNumber, insertedLineNumbers ? insertedLineNumbers + visualLines :
198    visualLines);
199
200  let newPos: number = member.getStart() + 1;
201  for (const [key, value] of slotMap) {
202    if (member.getStart() >= key) {
203      newPos += value;
204    }
205  }
206
207  const newContent: string = content.substring(0, newPos) + visualContent +
208    content.substring(newPos);
209  slotMap.set(member.getStart(), visualContent.length);
210  return newContent;
211}
212
213function insertVisualCodeBeforePos(member: ts.MethodDeclaration, visualContent: string,
214  content: string, oriContent: string): string {
215  const contentBeforePos: string = oriContent.substring(0, member.pos);
216  const originEtsFileLineNumber: number = contentBeforePos.split('\n').length;
217  const visualLines: number = visualContent.split('\n').length - 1;
218  const insertedLineNumbers: number = visualMap.get(originEtsFileLineNumber);
219  visualMap.set(originEtsFileLineNumber, insertedLineNumbers ? insertedLineNumbers + visualLines :
220    visualLines);
221  let newPos: number = member.pos;
222  for (const [key, value] of slotMap) {
223    if (member.pos >= key) {
224      newPos += value;
225    }
226  }
227  const newContent: string = content.substring(0, newPos) + visualContent +
228    content.substring(newPos);
229  slotMap.set(member.pos, visualContent.length);
230  return newContent;
231}
232
233function generateSourceMapForNewAndOriEtsFile(resourcePath: string, content: string) {
234  if (!process.env.cachePath) {
235    return;
236  }
237  const sourcemap: SourceMapGenerator = new SourceMapGenerator({
238    file: resourcePath
239  });
240  const lines: Array<string> = content.split('\n');
241  const originEtsFileLines: number = lines.length;
242  for (let l: number = 1; l <= originEtsFileLines; l++) {
243    let newEtsFileLineNumber: number = l;
244    for (const [originEtsFileLineNumber, visualLines] of visualMap) {
245      if (l > originEtsFileLineNumber) {
246        newEtsFileLineNumber += visualLines;
247      }
248    }
249    sourcemap.addMapping({
250      generated: {
251        line: newEtsFileLineNumber,
252        column: 0
253      },
254      source: resourcePath,
255      original: {
256        line: l,
257        column: 0
258      }
259    });
260  }
261  const visualMapName: string = path.parse(resourcePath).name + SUPERVISUAL_SOURCEMAP_EXT;
262  const visualDirPath: string = path.parse(resourcePath).dir;
263  const etsDirPath: string = path.parse(projectConfig.projectPath).dir;
264  const visualMapDirPath: string = path.resolve(process.env.cachePath, SUPERVISUAL +
265    visualDirPath.replace(etsDirPath, ''));
266  if (!(fs.existsSync(visualMapDirPath) && fs.statSync(visualMapDirPath).isDirectory())) {
267    mkDir(visualMapDirPath);
268  }
269  fs.writeFile(path.resolve(visualMapDirPath, visualMapName), sourcemap.toString(), (err) => {
270    if (err) {
271      return console.error(red, 'ERROR: Failed to write visual.js.map', reset);
272    }
273  });
274}
275
276function findVisualFile(filePath: string): string {
277  const etsDirPath: string = path.parse(projectConfig.projectPath).dir;
278  const visualDirPath: string = path.parse(projectConfig.aceSuperVisualPath).dir;
279  return filePath
280    .replace(projectConfig.projectPath, projectConfig.aceSuperVisualPath)
281    .replace(etsDirPath, visualDirPath).replace(/\.ets$/, '.visual');
282}
283
284function getVisualContent(visualPath: string, log: LogInfo[]): any {
285  const parseContent: any = genETS(fs.readFileSync(visualPath, 'utf-8'));
286  if (parseContent && parseContent.errorType && parseContent.errorType !== '') {
287    log.push({
288      type: LogType.ERROR,
289      message: parseContent.message
290    });
291  }
292  return parseContent ? parseContent.ets : null;
293}
294
295module.exports = preProcess;
296