• 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
16const WebSocket = require('ws');
17const ts = require('typescript');
18const path = require('path');
19const fs = require('fs');
20const _ = require('lodash');
21
22const { processComponentChild } = require('../lib/process_component_build');
23const { createWatchCompilerHost } = require('../lib/ets_checker');
24const { writeFileSync } = require('../lib/utils');
25const { projectConfig } = require('../main');
26const { props } = require('../lib/compile_info');
27
28const WebSocketServer = WebSocket.Server;
29
30let pluginSocket = '';
31
32let supplement = {
33  isAcceleratePreview: false,
34  line: 0,
35  column: 0,
36  fileName: ''
37}
38
39const pluginCommandChannelMessageHandlers = {
40  'compileComponent': handlePluginCompileComponent,
41  'default': () => {}
42};
43
44let previewCacheFilePath;
45const messages = [];
46let start = false;
47let checkStatus = false;
48let compileStatus = false;
49let receivedMsg_;
50let errorInfo;
51let compileWithCheck;
52let connectNum = 0;
53const maxConnectNum = 8;
54
55function init(port) {
56  previewCacheFilePath =
57    path.join(projectConfig.cachePath || projectConfig.buildPath, 'preview.ets');
58  const rootFileNames = [];
59  if (!fs.existsSync(previewCacheFilePath)) {
60    writeFileSync(previewCacheFilePath, '');
61  }
62  rootFileNames.push(previewCacheFilePath);
63  ts.createWatchProgram(
64    createWatchCompilerHost(rootFileNames, resolveDiagnostic, delayPrintLogCount, ()=>{}, true));
65  const wss = new WebSocketServer({
66    port: port,
67    host: '127.0.0.1'
68  });
69  wss.on('connection', function(ws) {
70    if (connectNum < maxConnectNum) {
71      connectNum++;
72      handlePluginConnect(ws);
73    } else {
74      ws.terminate();
75    }
76  });
77}
78
79function handlePluginConnect(ws) {
80  ws.on('message', function(message) {
81    pluginSocket = ws;
82    try {
83      const jsonData = JSON.parse(message);
84      handlePluginCommand(jsonData);
85    } catch(e) {
86    }
87  });
88}
89
90function handlePluginCommand(jsonData) {
91  pluginCommandChannelMessageHandlers[jsonData.command]
92    ? pluginCommandChannelMessageHandlers[jsonData.command](jsonData)
93    : pluginCommandChannelMessageHandlers['default'](jsonData);
94}
95
96function handlePluginCompileComponent(jsonData) {
97  if (jsonData) {
98    messages.push(jsonData);
99    if (receivedMsg_) {
100      return;
101    }
102  } else if (messages.length > 0){
103    jsonData = messages[0];
104  } else {
105    return
106  }
107  start = true;
108  const receivedMsg = _.cloneDeep(jsonData);
109  const compilerOptions = ts.readConfigFile(
110    path.resolve(__dirname, '../tsconfig.json'), ts.sys.readFile).config.compilerOptions;
111    Object.assign(compilerOptions, {
112      "sourceMap": false,
113    });
114  const sourceNode = ts.createSourceFile('preview.ets',
115    'struct preview{build(){' + receivedMsg.data.script + '}}',
116    ts.ScriptTarget.Latest, true, ts.ScriptKind.ETS, compilerOptions);
117  compileWithCheck = jsonData.data.compileWithCheck || 'true';
118  if (previewCacheFilePath && fs.existsSync(previewCacheFilePath)
119    && compileWithCheck === 'true') {
120      writeFileSync(previewCacheFilePath, 'struct preview{build(){' + receivedMsg.data.script + '}}');
121  }
122  const previewStatements = [];
123  const log = [];
124  supplement = {
125    isAcceleratePreview: true,
126    line: parseInt(JSON.parse(receivedMsg.data.offset).line),
127    column: parseInt(JSON.parse(receivedMsg.data.offset).column),
128    fileName: receivedMsg.data.filePath || ''
129  }
130  processComponentChild(sourceNode.statements[0].members[1].body, previewStatements, log, supplement);
131  supplement.isAcceleratePreview = false;
132  const newSource = ts.factory.updateSourceFile(sourceNode, previewStatements);
133  const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
134  const result = printer.printNode(ts.EmitHint.Unspecified, newSource, newSource);
135  receivedMsg.data.script = ts.transpileModule(result, {}).outputText;
136  receivedMsg.data.log = log;
137  if (pluginSocket.readyState === WebSocket.OPEN){
138    compileStatus = true;
139    receivedMsg_ = receivedMsg;
140    responseToPlugin();
141  }
142}
143
144function resolveDiagnostic(diagnostic) {
145  errorInfo = [];
146  const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
147  if (validateError(message)) {
148    if (diagnostic.file) {
149      const { line, character } =
150        diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
151        errorInfo.push(
152          `ArkTS:ERROR File: ${diagnostic.file.fileName}:${line + 1}:${character + 1}\n ${message}\n`);
153    } else {
154      errorInfo.push(`ArkTS:ERROR: ${message}`);
155    }
156  }
157}
158
159function delayPrintLogCount() {
160  if (start == true) {
161    checkStatus = true;
162    responseToPlugin();
163  }
164}
165
166function responseToPlugin() {
167  if ((compileWithCheck !== "true" && compileStatus == true) ||
168    (compileWithCheck === "true" && compileStatus == true && checkStatus == true) ) {
169    if (receivedMsg_) {
170      if (errorInfo) {
171        receivedMsg_.data.log =  receivedMsg_.data.log || [];
172        receivedMsg_.data.log.push(...errorInfo);
173      }
174      pluginSocket.send(JSON.stringify(receivedMsg_), (err) => {
175        start = false;
176        checkStatus = false;
177        compileStatus = false;
178        errorInfo = undefined;
179        receivedMsg_ = undefined;
180        messages.shift();
181        if (messages.length > 0) {
182          handlePluginCompileComponent();
183        }
184      });
185    }
186  }
187}
188
189function validateError(message) {
190  const propInfoReg = /Cannot find name\s*'(\$?\$?[_a-zA-Z0-9]+)'/;
191  const stateInfoReg = /Property\s*'(\$?[_a-zA-Z0-9]+)' does not exist on type/;
192  if (matchMessage(message, props, propInfoReg) ||
193    matchMessage(message, props, stateInfoReg)) {
194    return false;
195  }
196  return true;
197}
198
199function matchMessage(message, nameArr, reg) {
200  if (reg.test(message)) {
201    const match = message.match(reg);
202    if (match[1] && nameArr.includes(match[1])) {
203      return true;
204    }
205  }
206  return false;
207}
208
209module.exports = {
210  init
211};