@aiot-toolkit/parser 2.0.6-beta.9 → 2.1.0-prender.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,5 @@
1
1
  import { Dictionary, Logger } from '@aiot-toolkit/shared-utils';
2
+ import { IAttributeNode } from '../interface/IIdentifierNode';
2
3
  interface IElement {
3
4
  /**
4
5
  * 别名
@@ -8,6 +9,19 @@ interface IElement {
8
9
  * @example <img /> 生成为 image
9
10
  */
10
11
  aliasName?: string;
12
+ /**
13
+ * 别名属性
14
+ *
15
+ * aliasName 生效时,会将别名属性合并到属性列表中
16
+ *
17
+ * @example
18
+ * ```
19
+ * // <button /> 生成为 <input type="button" />
20
+ * aliasName='input'
21
+ * aliasAttributes=[{name:'type',value:'button'}]
22
+ * ```
23
+ */
24
+ aliasAttributes?: IAttributeNode[];
11
25
  /**
12
26
  * 允许文字子节点
13
27
  *
@@ -1321,6 +1321,12 @@ const STYLE_ATTRIBUTE_CONFIG = exports.STYLE_ATTRIBUTE_CONFIG = {
1321
1321
  scrollSnapAlign: {
1322
1322
  // csstree-validator可校验
1323
1323
  // 按照一般转换即可,无需额外转换
1324
+ },
1325
+ selectedFontSize: {
1326
+ validate: validateLengthNode
1327
+ },
1328
+ selectedBackgroundColor: {
1329
+ validate: validColorNode
1324
1330
  }
1325
1331
  };
1326
1332
  /**
@@ -217,6 +217,17 @@ const elementConfig = {
217
217
  name: {}
218
218
  }
219
219
  },
220
+ button: {
221
+ aliasName: 'input',
222
+ aliasAttributes: [{
223
+ name: 'type',
224
+ value: 'button'
225
+ }],
226
+ attributes: {
227
+ name: {},
228
+ value: {}
229
+ }
230
+ },
220
231
  input: {
221
232
  events: ['change', 'enterkeyclick', 'selectionchange'],
222
233
  attributes: {
@@ -804,6 +815,14 @@ const elementConfig = {
804
815
  enums: ['none', 'forwards']
805
816
  }
806
817
  }
818
+ },
819
+ 'widget-container': {
820
+ events: ['load'],
821
+ attributes: {
822
+ uri: {},
823
+ size: {},
824
+ uidata: {}
825
+ }
807
826
  }
808
827
  }
809
828
  };
@@ -0,0 +1,49 @@
1
+ /**
2
+ * IE2eConfig
3
+ */
4
+ export default interface IE2eConfig {
5
+ /**
6
+ * 测试文件的目录
7
+ *
8
+ * 绝对路径 或 相对config文件的路径
9
+ *
10
+ * @default "test"
11
+ */
12
+ dir: string;
13
+ /**
14
+ * 测试模式打包,入口文件的在manifest中的名称
15
+ */
16
+ entry: {
17
+ /**
18
+ * 自动化测试文件路径:相对config文件的路径 或 绝对路径
19
+ *
20
+ * @example "pages/index"
21
+ */
22
+ path: string;
23
+ launchMode?: string;
24
+ };
25
+ /**
26
+ * 待测试的页面配置
27
+ */
28
+ testcases?: Array<ITestCase>;
29
+ }
30
+ export interface ITestCase {
31
+ /**
32
+ * 页面名称, 对应manifest中的路由名称
33
+ *
34
+ * @example "pages/index"
35
+ */
36
+ name: string;
37
+ /**
38
+ * 测试完成后,等待多久回退到上一个页面,单位ms
39
+ *
40
+ * @default 0
41
+ */
42
+ sleep?: number;
43
+ /**
44
+ * 测试完成后,是否回退到上一个页面
45
+ *
46
+ * @default true
47
+ */
48
+ back?: boolean;
49
+ }
@@ -0,0 +1 @@
1
+ "use strict";
@@ -11,6 +11,7 @@ var _TemplateUtil = _interopRequireDefault(require("../translate/vela/utils/Temp
11
11
  var _UxUtil = _interopRequireDefault(require("../utils/UxUtil"));
12
12
  var _ScriptParser = _interopRequireDefault(require("./ScriptParser"));
13
13
  var _StyleParser = _interopRequireDefault(require("./StyleParser"));
14
+ var _fsExtra = _interopRequireDefault(require("fs-extra"));
14
15
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
15
16
  function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
16
17
  function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
@@ -72,14 +73,51 @@ class UxParser {
72
73
  };
73
74
  break;
74
75
  case 'style':
75
- const cssContent = parse5.serialize(childNode);
76
+ // 如果 style 标签有 src 属性,则读取对应的文件内容,否则直接使用 style 标签的内容
77
+ const src = _UxUtil.default.getStyleSrc(childNode, this.options.filePath);
78
+ let cssContent = '';
79
+ const filePath = this.options.filePath;
80
+ if (src) {
81
+ // 如果有 src 属性,且 style 标签内有内容,则提示用户 style 标签内的内容会被 src 文件覆盖
82
+ if (childNode.childNodes && childNode.childNodes.length > 0) {
83
+ this.options.onLog({
84
+ level: _sharedUtils.Loglevel.WARN,
85
+ filePath: this.options.filePath,
86
+ position: {
87
+ startLine: childNode.sourceCodeLocation?.startLine || 0,
88
+ startColumn: 0
89
+ },
90
+ message: [`style tag with src should not have inline content, src file will be used directly`]
91
+ });
92
+ }
93
+ // 读取 src 文件内容,如果文件不存在,则提示用户
94
+ if (!_fsExtra.default.existsSync(src)) {
95
+ this.options.onLog({
96
+ level: _sharedUtils.Loglevel.WARN,
97
+ filePath: this.options.filePath,
98
+ position: {
99
+ startLine: childNode.sourceCodeLocation?.startLine || 0,
100
+ startColumn: 0
101
+ },
102
+ message: [`style src file does not exist: ${src}`]
103
+ });
104
+ } else {
105
+ cssContent = _fsExtra.default.readFileSync(src).toString();
106
+ // 解析 css 时需要用到文件路径,所以将options.filePath暂时替换为src,解析完成后再还原
107
+ this.options.filePath = src;
108
+ }
109
+ } else cssContent = parse5.serialize(childNode);
110
+
76
111
  // 获取lang值,组合成文件名
77
112
  const langType = _UxUtil.default.getLangType(childNode);
78
- const cssFileName = _UxUtil.default.spliceFileName(fileName, langType);
113
+ // 使用 src 时,cssFileName src 的文件名为准,否则以当前解析的文件名为准
114
+ const cssFileName = src ? _path.default.basename(src) : _UxUtil.default.spliceFileName(fileName, langType);
79
115
  // 转换全局变量为符合lang类型的字符串
80
116
  const globalStyle = _UxUtil.default.convertGlobalJson(this.globalVar, langType);
81
117
  // 解析CSS 转为 js格式的内容
82
118
  const cssParserResult = await new _StyleParser.default(this.options, this.compilerOption, this.collectImageResource, childNode.sourceCodeLocation?.startLine || 0).parser(`${cssContent}\n${globalStyle}`, cssFileName);
119
+ // 还原 this.options.filePath
120
+ this.options.filePath = filePath;
83
121
  // 多Style标签(TODO: 应该不支持多style标签)
84
122
  ast.style.styleContent.push(...cssParserResult.ast.styleContent);
85
123
  ast.style.content = parse5.serialize(childNode);
@@ -12,8 +12,9 @@ declare class ScriptToTypescript implements ITranslate<IScriptAst, SourceFile> {
12
12
  options: IOptions;
13
13
  readonly compilerOption: ITranslateOption;
14
14
  readonly context: IFileLaneContext;
15
+ readonly disableCheckCode: boolean;
15
16
  private _systemFeatures;
16
- constructor(options: IOptions, compilerOption: ITranslateOption, context: IFileLaneContext);
17
+ constructor(options: IOptions, compilerOption: ITranslateOption, context: IFileLaneContext, disableCheckCode?: boolean);
17
18
  translate(sourceTree: IScriptAst, offsetList: IOffset[]): Promise<{
18
19
  targetTree: SourceFile;
19
20
  mapList: never[];
@@ -21,19 +21,23 @@ const BabelPreset = require('@babel/preset-env');
21
21
  class ScriptToTypescript {
22
22
  _systemFeatures = (() => new Set())();
23
23
  constructor(options, compilerOption, context) {
24
+ let disableCheckCode = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
24
25
  this.options = options;
25
26
  this.compilerOption = compilerOption;
26
27
  this.context = context;
28
+ this.disableCheckCode = disableCheckCode;
27
29
  }
28
30
  async translate(sourceTree,
29
31
  // eslint-disable-next-line
30
32
  offsetList) {
31
33
  // 检查代码错误
32
- const logs = await this.checkCode(sourceTree);
33
- if (logs) {
34
- logs.forEach(log => {
35
- this.options.onLog(log);
36
- });
34
+ if (!this.disableCheckCode) {
35
+ const logs = await this.checkCode(sourceTree);
36
+ if (logs) {
37
+ logs.forEach(log => {
38
+ this.options.onLog(log);
39
+ });
40
+ }
37
41
  }
38
42
  // 使用babel实现替换,由此实现自动更新source map
39
43
  const content = this.babelScript(sourceTree);
@@ -64,8 +68,11 @@ class ScriptToTypescript {
64
68
  compilerOption: this.compilerOption
65
69
  }]];
66
70
  const pluginList = [{
67
- enable: this.compilerOption.enableE2e,
71
+ enable: this.compilerOption.enableE2e && this.compilerOption.e2eConfigPath,
68
72
  path: _path.default.resolve(__dirname, './plugins/e2e.js')
73
+ }, {
74
+ enable: this.compilerOption.enableE2e && !this.compilerOption.e2eConfigPath,
75
+ path: _path.default.resolve(__dirname, './plugins/e2e1.js')
69
76
  }, {
70
77
  enable: this.compilerOption.startPage && this.options.fileType === 'app',
71
78
  path: _path.default.resolve(__dirname, './plugins/startPage.js')
@@ -88,7 +95,7 @@ class ScriptToTypescript {
88
95
  targets: {
89
96
  node: 8
90
97
  },
91
- modules: 'commonjs'
98
+ modules: false
92
99
  }]],
93
100
  plugins
94
101
  });
@@ -99,7 +106,7 @@ class ScriptToTypescript {
99
106
  let position = undefined;
100
107
  let message = babelError.message;
101
108
  if (loc) {
102
- const offsetLine = (sourceTree.sourceCodeLocation?.startLine || 0) - 1;
109
+ const offsetLine = (sourceTree.sourceCodeLocation?.startLine || 1) - 1;
103
110
  const startLine = loc.line + offsetLine;
104
111
  const startColumn = loc.column;
105
112
  // babel 错误信息中包含行列信息,需要替换
@@ -8,7 +8,7 @@ var t = _interopRequireWildcard(require("@babel/types"));
8
8
  var parser = _interopRequireWildcard(require("@babel/parser"));
9
9
  var _path = _interopRequireDefault(require("path"));
10
10
  var _fsExtra = _interopRequireDefault(require("fs-extra"));
11
- var _sharedUtils = require("@aiot-toolkit/shared-utils");
11
+ var _UxUtil = _interopRequireDefault(require("../../../utils/UxUtil"));
12
12
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
13
13
  function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
14
14
  function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
@@ -62,10 +62,6 @@ function addAppTestCode(path) {
62
62
  * b. 设置测试函数: 如果 onTestStart 方法已存在,则在其内部添加测试代码;否则,添加 onTestStart方法
63
63
  *
64
64
  *
65
- * 2. 如果启用自动路由,且当前页面是首页
66
- * a. 从routerList 中过滤出有测试文件的路由
67
- * b. 在末尾添加 $generateAutoRunTest
68
- * c. 在onShow 方法中调用 $generateAutoRunTest
69
65
  *
70
66
  *
71
67
  * @description 示例
@@ -91,12 +87,20 @@ function addAppTestCode(path) {
91
87
  * @param opts
92
88
  */
93
89
  function addPageTestCode(path, opts) {
94
- const e2eConfig = getE2eConfig(opts);
95
- const testFilePath = getTestFilePath(opts.options.filePath, opts, e2eConfig);
90
+ if (!opts.compilerOption.e2eConfigPath) {
91
+ return;
92
+ }
93
+ const e2eConfig = _UxUtil.default.getE2eConfig({
94
+ projectPath: opts.options.projectPath,
95
+ e2eConfigPath: opts.compilerOption.e2eConfigPath
96
+ });
97
+ const filePath = opts.options.filePath;
98
+ const testFilePath = getTestFilePath(filePath, opts, e2eConfig);
99
+ const testCaseConfig = getTestcaseConfig(filePath, opts, e2eConfig);
96
100
  // 1
97
101
  if (_fsExtra.default.existsSync(testFilePath)) {
98
102
  // 1.a
99
- const relativePath = _path.default.relative(_path.default.dirname(opts.options.filePath), testFilePath);
103
+ const relativePath = _path.default.relative(_path.default.dirname(filePath), testFilePath);
100
104
  const vmImport = t.importDeclaration([t.importDefaultSpecifier(t.identifier('fnTestCase'))], t.stringLiteral(relativePath));
101
105
  if (path.parent.type === 'Program') {
102
106
  path.parent.body.unshift(vmImport);
@@ -105,36 +109,21 @@ function addPageTestCode(path, opts) {
105
109
  // 1.b
106
110
  if (path.node.declaration.type === 'ObjectExpression') {
107
111
  const properties = path.node.declaration.properties;
108
- insertFun('onTestStart', generateTestFn(), properties);
109
- }
110
- }
111
-
112
- // 2
113
- if (isIndexPage(opts) && e2eConfig.autoRoute) {
114
- let {
115
- routerList,
116
- pageSleep
117
- } = e2eConfig.autoRoute;
118
- routerList = routerList?.filter(item => hasTestFile(item, opts, e2eConfig));
119
- if (!routerList?.length) {
120
- opts.options.onLog({
121
- level: _sharedUtils.Loglevel.WARN,
122
- message: 'missing autoRoute.routerList'
123
- });
124
- return;
125
- }
126
- // 2.a
127
-
128
- if (path.node.declaration.type === 'ObjectExpression') {
129
- const properties = path.node.declaration.properties;
130
-
131
- // 2.b
132
- insertFun('$generateAutoRunTest', generateAutoRouterFn(pageSleep, routerList), properties);
133
- // 2.c
134
- insertFun('onShow', generateAutoRouterInOnShow(), properties);
112
+ insertFn('onTestStart', generateTestFn(testCaseConfig), properties);
135
113
  }
136
114
  }
137
115
  }
116
+ function getTestcaseConfig(filePath, opts, e2eConfig) {
117
+ const {
118
+ projectPath
119
+ } = opts.options;
120
+ const {
121
+ sourceRoot
122
+ } = opts.compilerOption;
123
+ const relativeSrc = _path.default.relative(_path.default.join(projectPath, sourceRoot || ''), filePath);
124
+ const pageName = _path.default.dirname(relativeSrc).replace(/\\/g, _path.default.posix.sep);
125
+ return e2eConfig.testcases?.find(item => item.name === pageName);
126
+ }
138
127
 
139
128
  /**
140
129
  * 插入函数
@@ -144,7 +133,7 @@ function addPageTestCode(path, opts) {
144
133
  * @param name 函数名
145
134
  * @param body 函数体
146
135
  */
147
- function insertFun(name, body, properties) {
136
+ function insertFn(name, body, properties) {
148
137
  const fun = properties.find(p => p.type === 'ObjectMethod' && p.key.name === name);
149
138
  if (fun) {
150
139
  fun.body.body.push(...body.body);
@@ -155,77 +144,6 @@ function insertFun(name, body, properties) {
155
144
  }, [], body));
156
145
  }
157
146
  }
158
- function hasTestFile(router, opts, e2eConfig) {
159
- const manifest = getManifest(opts);
160
- if (manifest) {
161
- const {
162
- pages
163
- } = manifest.router;
164
- const pageRouter = pages[router];
165
- if (!pageRouter) {
166
- opts.options.onLog({
167
- level: _sharedUtils.Loglevel.WARN,
168
- message: [{
169
- word: router
170
- }, `missing router in manifest.json`]
171
- });
172
- return false;
173
- }
174
- const pagePath = `${opts.compilerOption.sourceRoot}/${router}/${pageRouter.component}`;
175
- const testFilePath = getTestFilePath(pagePath, opts, e2eConfig);
176
- return _fsExtra.default.existsSync(testFilePath);
177
- }
178
- return false;
179
- }
180
- function isIndexPage(opts) {
181
- const manifest = getManifest(opts);
182
- if (manifest) {
183
- const {
184
- entry,
185
- pages
186
- } = manifest.router;
187
- const entryItem = pages[entry];
188
- const entryPath = `${entry}/${entryItem.component}`;
189
- const sourcePath = _path.default.join(opts.options.projectPath, opts.compilerOption.sourceRoot);
190
- const filePath = _path.default.relative(sourcePath, opts.options.filePath);
191
- const filePathParsed = _path.default.parse(filePath);
192
- return entryPath === `${filePathParsed.dir}/${filePathParsed.name}`;
193
- }
194
- return false;
195
- }
196
- function getManifest(opts) {
197
- const manifestPath = _path.default.resolve(opts.options.projectPath, opts.compilerOption.sourceRoot, 'manifest.json');
198
- if (manifestPath) {
199
- return _fsExtra.default.readJSONSync(manifestPath);
200
- }
201
- }
202
- function getE2eConfig(opts) {
203
- const defaultTestDir = 'test';
204
- let result = {
205
- testDir: _path.default.join(opts.options.projectPath, defaultTestDir)
206
- };
207
- const {
208
- projectPath
209
- } = opts.options;
210
- const {
211
- e2eConfigPath
212
- } = opts.compilerOption;
213
- if (e2eConfigPath) {
214
- const configFilePath = _path.default.resolve(projectPath, e2eConfigPath);
215
- const configFileDir = _path.default.dirname(configFilePath);
216
- if (_fsExtra.default.existsSync(configFilePath)) {
217
- result = _fsExtra.default.readJSONSync(configFilePath, 'utf-8');
218
-
219
- // 目录转换为绝对路径
220
- if (result) {
221
- result.testDir = _path.default.resolve(configFileDir, result.testDir || defaultTestDir);
222
- }
223
- } else {
224
- throw new Error(`e2e Configuration file does not exist: ${configFilePath}`);
225
- }
226
- }
227
- return result;
228
- }
229
147
 
230
148
  /**
231
149
  * 获取源文件对应的测试文件地址
@@ -243,14 +161,18 @@ function getTestFilePath(sourceFile, opts, e2eConfig) {
243
161
  } = opts.compilerOption;
244
162
  const relativeSrc = _path.default.relative(_path.default.join(projectPath, sourceRoot || ''), sourceFile);
245
163
  const parsed = _path.default.parse(relativeSrc);
246
- return _path.default.join(e2eConfig.testDir, parsed.dir, `${parsed.name}.js`);
164
+ return _path.default.join(e2eConfig.dir, parsed.dir, `${parsed.name}.js`);
247
165
  }
248
166
 
249
167
  /**
250
168
  * 获取测试函数的函数体
251
169
  * @returns
252
170
  */
253
- function generateTestFn() {
171
+ function generateTestFn(config) {
172
+ const {
173
+ sleep,
174
+ back = true
175
+ } = config || {};
254
176
  const fn = `function(){
255
177
  this.$options = this._options || this.$options;
256
178
  if (this.$options._descriptor) {
@@ -262,46 +184,16 @@ function generateTestFn() {
262
184
  global.qat = new VelaQuickappTest();
263
185
  setTimeout(() => {
264
186
  typeof fnTestCase === 'function' && fnTestCase(this)
265
- qat.run(() => {
187
+ qat.run(async () => {
266
188
  if (this.back !== "false") {
267
189
  console.info("拥有关联测试用例,测试完毕,返回到之前的页面");
268
190
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
269
191
  var _system = _interopRequireDefault($app_require$('@app-module/system.router'));
270
- _system.default.back();
192
+ ${sleep ? `await new Promise(resolve => setTimeout(resolve, ${sleep}));` : ''}
193
+ ${back ? `_system.default.back();` : ''}
271
194
  }
272
195
  });
273
196
  }, global.CASE_TEST_START);
274
197
  }`;
275
198
  return parser.parseExpression(fn).body;
276
- }
277
- function generateAutoRouterFn() {
278
- let sleep = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 3000;
279
- let routerList = arguments.length > 1 ? arguments[1] : undefined;
280
- const fn = `function(){
281
- const routerList = ${JSON.stringify(routerList)}
282
- if (!routerList) {
283
- console.log('missing routerList');
284
- return;
285
- }
286
- if (this.autoTestPageIndex === undefined) {
287
- this.autoTestPageIndex = -1;
288
- }
289
-
290
- setTimeout(() => {
291
- if (++this.autoTestPageIndex < routerList.length) {
292
- const uri = routerList[this.autoTestPageIndex];
293
- console.log(\`auto test page=\${uri}, index=\${this.autoTestPageIndex}\`);
294
- router.push({ uri });
295
- } else {
296
- console.log('auto test finish');
297
- }
298
- }, ${sleep});
299
- }`;
300
- return parser.parseExpression(fn).body;
301
- }
302
- function generateAutoRouterInOnShow() {
303
- const fn = `function(){
304
- this.$generateAutoRunTest();
305
- }`;
306
- return parser.parseExpression(fn).body;
307
199
  }
@@ -0,0 +1,8 @@
1
+ import { PluginObj, PluginPass } from '@babel/core';
2
+ /**
3
+ * 用于添加应用自动化测试代码
4
+ * 分为两种情况
5
+ * 1. app文件
6
+ * 2. page 文件
7
+ */
8
+ export default function e2e(): PluginObj<PluginPass>;
@@ -0,0 +1,307 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = e2e;
7
+ var t = _interopRequireWildcard(require("@babel/types"));
8
+ var parser = _interopRequireWildcard(require("@babel/parser"));
9
+ var _path = _interopRequireDefault(require("path"));
10
+ var _fsExtra = _interopRequireDefault(require("fs-extra"));
11
+ var _sharedUtils = require("@aiot-toolkit/shared-utils");
12
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
13
+ function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
14
+ function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
15
+ /**
16
+ * 用于添加应用自动化测试代码
17
+ * 分为两种情况
18
+ * 1. app文件
19
+ * 2. page 文件
20
+ */
21
+ function e2e() {
22
+ return {
23
+ visitor: {
24
+ ExportDefaultDeclaration(path, _ref) {
25
+ let {
26
+ opts
27
+ } = _ref;
28
+ const opts2 = opts;
29
+ const options = opts2.options;
30
+ switch (options.fileType) {
31
+ case 'page':
32
+ addPageTestCode(path, opts2);
33
+ break;
34
+ case 'app':
35
+ addAppTestCode(path);
36
+ break;
37
+ }
38
+ }
39
+ }
40
+ };
41
+ }
42
+
43
+ /**
44
+ * 添加 app 的测试代码
45
+ *
46
+ * 引入测试框架文件
47
+ * @param path
48
+ * @param opts
49
+ */
50
+ function addAppTestCode(path) {
51
+ const runtimePath = _path.default.join(__dirname, '../runtime/velaTestLibrary.js');
52
+ const appImport = t.importDeclaration([], t.stringLiteral(runtimePath));
53
+ if (path.parent.type === 'Program') {
54
+ path.parent.body.unshift(appImport);
55
+ }
56
+ }
57
+
58
+ /**
59
+ * 添加页面的测试代码
60
+ * 1. 如果测试文件存在,则
61
+ * a. import 测试文件
62
+ * b. 设置测试函数: 如果 onTestStart 方法已存在,则在其内部添加测试代码;否则,添加 onTestStart方法
63
+ *
64
+ *
65
+ * 2. 如果启用自动路由,且当前页面是首页
66
+ * a. 从routerList 中过滤出有测试文件的路由
67
+ * b. 在末尾添加 $generateAutoRunTest
68
+ * c. 在onShow 方法中调用 $generateAutoRunTest
69
+ *
70
+ *
71
+ * @description 示例
72
+ *
73
+ * 源码 `src/home/myHome.ux`
74
+ * ```
75
+ *
76
+ * export default {}
77
+ * ```
78
+ *
79
+ * 产物
80
+ * ```
81
+ * import fnTestCase from 'test/home/myHome.js'
82
+ *
83
+ * export default {
84
+ * onTestStart: (){
85
+ * // code
86
+ * }
87
+ * }
88
+ * ```
89
+ *
90
+ * @param path
91
+ * @param opts
92
+ */
93
+ function addPageTestCode(path, opts) {
94
+ const e2eConfig = getE2eConfig(opts);
95
+ const testFilePath = getTestFilePath(opts.options.filePath, opts, e2eConfig);
96
+ // 1
97
+ if (_fsExtra.default.existsSync(testFilePath)) {
98
+ // 1.a
99
+ const relativePath = _path.default.relative(_path.default.dirname(opts.options.filePath), testFilePath);
100
+ const vmImport = t.importDeclaration([t.importDefaultSpecifier(t.identifier('fnTestCase'))], t.stringLiteral(relativePath));
101
+ if (path.parent.type === 'Program') {
102
+ path.parent.body.unshift(vmImport);
103
+ }
104
+
105
+ // 1.b
106
+ if (path.node.declaration.type === 'ObjectExpression') {
107
+ const properties = path.node.declaration.properties;
108
+ insertFun('onTestStart', generateTestFn(), properties);
109
+ }
110
+ }
111
+
112
+ // 2
113
+ if (isIndexPage(opts) && e2eConfig.autoRoute) {
114
+ let {
115
+ routerList,
116
+ pageSleep
117
+ } = e2eConfig.autoRoute;
118
+ routerList = routerList?.filter(item => hasTestFile(item, opts, e2eConfig));
119
+ if (!routerList?.length) {
120
+ opts.options.onLog({
121
+ level: _sharedUtils.Loglevel.WARN,
122
+ message: 'missing autoRoute.routerList'
123
+ });
124
+ return;
125
+ }
126
+ // 2.a
127
+
128
+ if (path.node.declaration.type === 'ObjectExpression') {
129
+ const properties = path.node.declaration.properties;
130
+
131
+ // 2.b
132
+ insertFun('$generateAutoRunTest', generateAutoRouterFn(pageSleep, routerList), properties);
133
+ // 2.c
134
+ insertFun('onShow', generateAutoRouterInOnShow(), properties);
135
+ }
136
+ }
137
+ }
138
+
139
+ /**
140
+ * 插入函数
141
+ *
142
+ * 如果已存在,把函数体插入到指定函数末尾;不存在,则添加
143
+ *
144
+ * @param name 函数名
145
+ * @param body 函数体
146
+ */
147
+ function insertFun(name, body, properties) {
148
+ const fun = properties.find(p => p.type === 'ObjectMethod' && p.key.name === name);
149
+ if (fun) {
150
+ fun.body.body.push(...body.body);
151
+ } else {
152
+ properties.push(t.objectMethod('method', {
153
+ name: name,
154
+ type: 'Identifier'
155
+ }, [], body));
156
+ }
157
+ }
158
+ function hasTestFile(router, opts, e2eConfig) {
159
+ const manifest = getManifest(opts);
160
+ if (manifest) {
161
+ const {
162
+ pages
163
+ } = manifest.router;
164
+ const pageRouter = pages[router];
165
+ if (!pageRouter) {
166
+ opts.options.onLog({
167
+ level: _sharedUtils.Loglevel.WARN,
168
+ message: [{
169
+ word: router
170
+ }, `missing router in manifest.json`]
171
+ });
172
+ return false;
173
+ }
174
+ const pagePath = `${opts.compilerOption.sourceRoot}/${router}/${pageRouter.component}`;
175
+ const testFilePath = getTestFilePath(pagePath, opts, e2eConfig);
176
+ return _fsExtra.default.existsSync(testFilePath);
177
+ }
178
+ return false;
179
+ }
180
+ function isIndexPage(opts) {
181
+ const manifest = getManifest(opts);
182
+ if (manifest) {
183
+ const {
184
+ entry,
185
+ pages
186
+ } = manifest.router;
187
+ const entryItem = pages[entry];
188
+ const entryPath = `${entry}/${entryItem.component}`;
189
+ const sourcePath = _path.default.join(opts.options.projectPath, opts.compilerOption.sourceRoot);
190
+ const filePath = _path.default.relative(sourcePath, opts.options.filePath);
191
+ const filePathParsed = _path.default.parse(filePath);
192
+ return entryPath === `${filePathParsed.dir}/${filePathParsed.name}`;
193
+ }
194
+ return false;
195
+ }
196
+ function getManifest(opts) {
197
+ const manifestPath = _path.default.resolve(opts.options.projectPath, opts.compilerOption.sourceRoot, 'manifest.json');
198
+ if (manifestPath) {
199
+ return _fsExtra.default.readJSONSync(manifestPath);
200
+ }
201
+ }
202
+ function getE2eConfig(opts) {
203
+ const defaultTestDir = 'test';
204
+ let result = {
205
+ testDir: _path.default.join(opts.options.projectPath, defaultTestDir)
206
+ };
207
+ const {
208
+ projectPath
209
+ } = opts.options;
210
+ const {
211
+ e2eConfigPath
212
+ } = opts.compilerOption;
213
+ if (e2eConfigPath) {
214
+ const configFilePath = _path.default.resolve(projectPath, e2eConfigPath);
215
+ const configFileDir = _path.default.dirname(configFilePath);
216
+ if (_fsExtra.default.existsSync(configFilePath)) {
217
+ result = _fsExtra.default.readJSONSync(configFilePath, 'utf-8');
218
+
219
+ // 目录转换为绝对路径
220
+ if (result) {
221
+ result.testDir = _path.default.resolve(configFileDir, result.testDir || defaultTestDir);
222
+ }
223
+ } else {
224
+ throw new Error(`e2e Configuration file does not exist: ${configFilePath}`);
225
+ }
226
+ }
227
+ return result;
228
+ }
229
+
230
+ /**
231
+ * 获取源文件对应的测试文件地址
232
+ * @param sourceFile 源文件
233
+ * @param opts
234
+ * @param e2eConfig
235
+ * @returns
236
+ */
237
+ function getTestFilePath(sourceFile, opts, e2eConfig) {
238
+ const {
239
+ projectPath
240
+ } = opts.options;
241
+ const {
242
+ sourceRoot
243
+ } = opts.compilerOption;
244
+ const relativeSrc = _path.default.relative(_path.default.join(projectPath, sourceRoot || ''), sourceFile);
245
+ const parsed = _path.default.parse(relativeSrc);
246
+ return _path.default.join(e2eConfig.testDir, parsed.dir, `${parsed.name}.js`);
247
+ }
248
+
249
+ /**
250
+ * 获取测试函数的函数体
251
+ * @returns
252
+ */
253
+ function generateTestFn() {
254
+ const fn = `function(){
255
+ this.$options = this._options || this.$options;
256
+ if (this.$options._descriptor) {
257
+ this.$options._descriptor["back"] = {
258
+ access: "public",
259
+ };
260
+ }
261
+ global.CASE_TEST_START = global.CASE_TEST_START || 1000;
262
+ global.qat = new VelaQuickappTest();
263
+ setTimeout(() => {
264
+ typeof fnTestCase === 'function' && fnTestCase(this)
265
+ qat.run(() => {
266
+ if (this.back !== "false") {
267
+ console.info("拥有关联测试用例,测试完毕,返回到之前的页面");
268
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
269
+ var _system = _interopRequireDefault($app_require$('@app-module/system.router'));
270
+ _system.default.back();
271
+ }
272
+ });
273
+ }, global.CASE_TEST_START);
274
+ }`;
275
+ return parser.parseExpression(fn).body;
276
+ }
277
+ function generateAutoRouterFn() {
278
+ let sleep = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 3000;
279
+ let routerList = arguments.length > 1 ? arguments[1] : undefined;
280
+ const fn = `function(){
281
+ const routerList = ${JSON.stringify(routerList)}
282
+ if (!routerList) {
283
+ console.log('missing routerList');
284
+ return;
285
+ }
286
+ if (this.autoTestPageIndex === undefined) {
287
+ this.autoTestPageIndex = -1;
288
+ }
289
+
290
+ setTimeout(() => {
291
+ if (++this.autoTestPageIndex < routerList.length) {
292
+ const uri = routerList[this.autoTestPageIndex];
293
+ console.log(\`auto test page=\${uri}, index=\${this.autoTestPageIndex}\`);
294
+ router.push({ uri });
295
+ } else {
296
+ console.log('auto test finish');
297
+ }
298
+ }, ${sleep});
299
+ }`;
300
+ return parser.parseExpression(fn).body;
301
+ }
302
+ function generateAutoRouterInOnShow() {
303
+ const fn = `function(){
304
+ this.$generateAutoRunTest();
305
+ }`;
306
+ return parser.parseExpression(fn).body;
307
+ }
@@ -52,5 +52,9 @@ declare class SourceMapUtil {
52
52
  * @returns
53
53
  */
54
54
  static countStatementLines(originStr: string): number;
55
+ /**
56
+ * 拼接正文和source map
57
+ */
58
+ static concatSourceMap(content: string, sourceMap: string): string;
55
59
  }
56
60
  export default SourceMapUtil;
@@ -114,5 +114,11 @@ class SourceMapUtil {
114
114
  let lineCount = (originStr.match(/\n/g) || []).length + 1;
115
115
  return lineCount;
116
116
  }
117
+ /**
118
+ * 拼接正文和source map
119
+ */
120
+ static concatSourceMap(content, sourceMap) {
121
+ return `${content}\n//# sourceMappingURL=data:application/json;base64,${Buffer.from(sourceMap).toString('base64')}`;
122
+ }
117
123
  }
118
124
  var _default = exports.default = SourceMapUtil;
@@ -367,6 +367,7 @@ class TemplateUtil {
367
367
  }
368
368
  const elementConfig = getElementConfig(tag.name);
369
369
  const effectTag = elementConfig?.aliasName || tag.name;
370
+ const aliasAttributes = elementConfig?.aliasAttributes;
370
371
  const {
371
372
  globalInstance,
372
373
  dataInstance,
@@ -374,7 +375,7 @@ class TemplateUtil {
374
375
  } = this.context;
375
376
  const result = `${globalInstance}.${callerName}("${effectTag}", ${_sharedUtils.StringUtil.objectToString({
376
377
  [dataInstance]: dataInstanceValue,
377
- __opts__: await this.attributesToObject(attributes, identifiers, {
378
+ __opts__: await this.attributesToObject(aliasAttributes ? [...aliasAttributes, ...attributes] : attributes, identifiers, {
378
379
  nameToCamel: true
379
380
  })
380
381
  })}, ${children})`;
@@ -537,7 +537,7 @@ class StyleUtil {
537
537
  if (!(0, _StyleSelectorType.isSupportMediaType)(mediaTypeName, projectType)) {
538
538
  // 报错
539
539
  onLog({
540
- level: _sharedUtils.Loglevel.ERROR,
540
+ level: _sharedUtils.Loglevel.WARN,
541
541
  filePath: filePath,
542
542
  position: node.loc ? styleMapUil.transfromLocToPosition(node.loc) : undefined,
543
543
  message: [`Unsupported media type `, {
@@ -2,6 +2,7 @@ import { Dictionary } from '@aiot-toolkit/shared-utils';
2
2
  import { Element } from 'aiot-parse5/dist/tree-adapters/default';
3
3
  import IImportAst from '../interface/IImportAst';
4
4
  import ITranslateOption from '../interface/ITranslateOption';
5
+ import IE2eConfig from '../interface/IE2eConfig';
5
6
  /**
6
7
  * UxParserUtil
7
8
  */
@@ -47,5 +48,18 @@ declare class UxUtil {
47
48
  */
48
49
  static isAbsoluteResource(path?: string): boolean;
49
50
  static importToTypescript(importList: IImportAst[], compilerOption: ITranslateOption): string[];
51
+ static getE2eConfig(option: {
52
+ projectPath: string;
53
+ e2eConfigPath: string;
54
+ }): IE2eConfig;
55
+ /**
56
+ * 获取 style 标签的 src 属性值
57
+ * 如果 src 是相对路径,则需要拼接路径
58
+ *
59
+ * @param node
60
+ * @param filePath
61
+ * @returns
62
+ */
63
+ static getStyleSrc(node: Element, filePath: string): string;
50
64
  }
51
65
  export default UxUtil;
@@ -5,6 +5,7 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.default = void 0;
7
7
  var _path = _interopRequireDefault(require("path"));
8
+ var _fsExtra = _interopRequireDefault(require("fs-extra"));
8
9
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
9
10
  /**
10
11
  * UxParserUtil
@@ -97,7 +98,7 @@ class UxUtil {
97
98
  name,
98
99
  src
99
100
  } = item;
100
- const pushList = [`$app_exports$['${name.toLowerCase()}'] = require('${src}')`];
101
+ const pushList = [`import ${name.toLowerCase().replace(/-/g, '_')}_comp from '${src}'`];
101
102
 
102
103
  // 如果开启统计,添加引用组件耗时日志
103
104
  if (compilerOption.enableStats) {
@@ -108,5 +109,45 @@ class UxUtil {
108
109
  }
109
110
  return appImport;
110
111
  }
112
+ static getE2eConfig(option) {
113
+ const defaultTestDir = 'test';
114
+ const {
115
+ projectPath,
116
+ e2eConfigPath
117
+ } = option;
118
+ const configFilePath = _path.default.resolve(projectPath, e2eConfigPath);
119
+ const configFileDir = _path.default.dirname(configFilePath);
120
+ if (_fsExtra.default.existsSync(configFilePath)) {
121
+ const obj = _fsExtra.default.readJSONSync(configFilePath, 'utf-8');
122
+ const dir = _path.default.resolve(configFileDir, obj.dir || defaultTestDir);
123
+ if (!obj.entry) {
124
+ throw new Error(`e2e Configuration file error: missing entry field`);
125
+ }
126
+ const result = {
127
+ ...obj,
128
+ dir
129
+ };
130
+ return result;
131
+ } else {
132
+ throw new Error(`e2e Configuration file does not exist: ${configFilePath}`);
133
+ }
134
+ }
135
+
136
+ /**
137
+ * 获取 style 标签的 src 属性值
138
+ * 如果 src 是相对路径,则需要拼接路径
139
+ *
140
+ * @param node
141
+ * @param filePath
142
+ * @returns
143
+ */
144
+ static getStyleSrc(node, filePath) {
145
+ for (let attr of node.attrs) {
146
+ if (attr.name === 'src') {
147
+ return UxUtil.isAbsoluteResource(attr.value) ? attr.value : _path.default.join(_path.default.dirname(filePath), attr.value);
148
+ }
149
+ }
150
+ return '';
151
+ }
111
152
  }
112
153
  var _default = exports.default = UxUtil;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiot-toolkit/parser",
3
- "version": "2.0.6-beta.9",
3
+ "version": "2.1.0-prender.1",
4
4
  "description": "Parse the source code of aiot and convert it to the AST (Abstract Syntax Tree) of the target code.",
5
5
  "keywords": [
6
6
  "aiot",
@@ -20,7 +20,7 @@
20
20
  "test": "node ./__tests__/parser.test.js"
21
21
  },
22
22
  "dependencies": {
23
- "@aiot-toolkit/shared-utils": "2.0.6-beta.9",
23
+ "@aiot-toolkit/shared-utils": "2.1.0-prender.1",
24
24
  "@babel/core": "^7.23.6",
25
25
  "@babel/generator": "^7.24.10",
26
26
  "@babel/parser": "^7.24.8",
@@ -31,7 +31,7 @@
31
31
  "css-tree": "npm:aiot-css-tree@^2.3.1",
32
32
  "csstree-validator": "^3.0.0",
33
33
  "eslint": "^8.46.0",
34
- "file-lane": "2.0.6-beta.9",
34
+ "file-lane": "2.1.0-prender.1",
35
35
  "fs-extra": "^11.2.0",
36
36
  "google-protobuf": "^3.21.2",
37
37
  "less": "^4.2.0",
@@ -60,5 +60,5 @@
60
60
  "@types/tinycolor2": "^1.4.6",
61
61
  "babel-plugin-tester": "^11.0.4"
62
62
  },
63
- "gitHead": "18718f09f1ee7f1d7361022c5fb7858c87cee2bd"
63
+ "gitHead": "8bb39a59fbe7a7c22c1167e69d44c78024330cec"
64
64
  }