UNPKG

5.55 kBJavaScriptView Raw
1'use strict';
2const _ = require('lodash');
3const esprima = require('esprima');
4const rtError = require('./RTCodeError');
5const RTCodeError = rtError.RTCodeError;
6
7/**
8 * @param {string} code
9 * @param node
10 * @param {Context} context
11 */
12function validateJS(code, node, context) {
13 try {
14 esprima.parse(code);
15 } catch (e) {
16 throw RTCodeError.build(context, node, e.description);
17 }
18}
19
20/**
21 * @param {string} name
22 * @return {string}
23 */
24function normalizeName(name) {
25 return name.replace(/-/g, '_');
26}
27
28/**
29 * @param {string} txt
30 * @return {boolean}
31 */
32function isStringOnlyCode(txt) {
33 return /^\s*\{.*}\s*$/g.test(txt);
34 //txt = txt.trim();
35 //return txt.length && txt.charAt(0) === '{' && txt.charAt(txt.length - 1) === '}';
36}
37
38/**
39 * @param {Array.<*>} array
40 * @param {*} obj
41 */
42function addIfMissing(array, obj) {
43 if (!_.includes(array, obj)) {
44 array.push(obj);
45 }
46}
47
48/**
49 * @param {Array.<string>} children
50 * @return {string}
51 */
52function concatChildren(children) {
53 let res = '';
54 _.forEach(children, child => {
55 if (child && !_.startsWith(child, ' /*')) {
56 res += ',';
57 }
58 res += child;
59 });
60 return res;
61}
62
63/**
64 * validate rt
65 * @param options
66 * @param {*} context
67 * @param {CONTEXT} reportContext
68 * @param node
69 */
70function validate(options, context, reportContext, node) {
71 if (node.type === 'tag' && node.attribs['rt-if'] && !node.attribs.key) {
72 const loc = rtError.getNodeLoc(context, node);
73 reportContext.warn('rt-if without a key', options.fileName, loc.pos.line, loc.pos.col, loc.start, loc.end);
74 }
75 if (node.type === 'tag' && node.attribs['rt-require'] && (node.attribs.dependency || node.attribs.as)) {
76 const loc = rtError.getNodeLoc(context, node);
77 reportContext.warn("'rt-require' is obsolete, use 'rt-import' instead", options.fileName, loc.pos.line, loc.pos.col, loc.start, loc.end);
78 }
79 if (node.children) {
80 node.children.forEach(validate.bind(this, options, context, reportContext));
81 }
82}
83
84/**
85 * return true if any node in the given tree uses a scope name from the given set, false - otherwise.
86 * @param scopeNames a set of scope names to find
87 * @param node root of a syntax tree generated from an ExpressionStatement or one of its children.
88 */
89function usesScopeName(scopeNames, node) {
90 function usesScope(root) {
91 return usesScopeName(scopeNames, root);
92 }
93 if (_.isEmpty(scopeNames)) {
94 return false;
95 }
96 // rt-if="x"
97 if (node.type === 'Identifier') {
98 return _.includes(scopeNames, node.name);
99 }
100 // rt-if="e({key1: value1})"
101 if (node.type === 'Property') {
102 return usesScope(node.value);
103 }
104 // rt-if="e.x" or rt-if="e1[e2]"
105 if (node.type === 'MemberExpression') {
106 return node.computed ? usesScope(node.object) || usesScope(node.property) : usesScope(node.object);
107 }
108 // rt-if="!e"
109 if (node.type === 'UnaryExpression') {
110 return usesScope(node.argument);
111 }
112 // rt-if="e1 || e2" or rt-if="e1 | e2"
113 if (node.type === 'LogicalExpression' || node.type === 'BinaryExpression') {
114 return usesScope(node.left) || usesScope(node.right);
115 }
116 // rt-if="e1(e2, ... eN)"
117 if (node.type === 'CallExpression') {
118 return usesScope(node.callee) || _.some(node.arguments, usesScope);
119 }
120 // rt-if="f({e1: e2})"
121 if (node.type === 'ObjectExpression') {
122 return _.some(node.properties, usesScope);
123 }
124 // rt-if="e1[e2]"
125 if (node.type === 'ArrayExpression') {
126 return _.some(node.elements, usesScope);
127 }
128 return false;
129}
130
131
132/**
133 * @const
134 */
135const curlyMap = {'{': 1, '}': -1};
136
137/**
138 * @typedef {{boundParams: Array.<string>, injectedFunctions: Array.<string>, html: string, options: *}} Context
139 */
140
141/**
142 * @typedef {{fileName:string,force:boolean,modules:string,defines:*,reactImportPath:string=,lodashImportPath:string=,flow:boolean,name:string,native:boolean,propTemplates:*,format:string,_:*,version:boolean,help:boolean,listTargetVersion:boolean,modules:string, dryRun:boolean}} Options
143 */
144
145/**
146 * @param node
147 * @param {Context} context
148 * @param {string} txt
149 * @return {string}
150 */
151function convertText(node, context, txt) {
152 let res = '';
153 let first = true;
154 const concatChar = node.type === 'text' ? ',' : '+';
155 while (_.includes(txt, '{')) {
156 const start = txt.indexOf('{');
157 const pre = txt.substr(0, start);
158 if (pre) {
159 res += (first ? '' : concatChar) + JSON.stringify(pre);
160 first = false;
161 }
162 let curlyCounter = 1;
163 let end = start;
164 while (++end < txt.length && curlyCounter > 0) {
165 curlyCounter += curlyMap[txt.charAt(end)] || 0;
166 }
167 if (curlyCounter === 0) {
168 const needsParens = start !== 0 || end !== txt.length - 1;
169 res += (first ? '' : concatChar) + (needsParens ? '(' : '') + txt.substr(start + 1, end - start - 2) + (needsParens ? ')' : '');
170 first = false;
171 txt = txt.substr(end);
172 } else {
173 throw RTCodeError.build(context, node, `Failed to parse text '${txt}'`);
174 }
175 }
176 if (txt) {
177 res += (first ? '' : concatChar) + JSON.stringify(txt);
178 }
179 if (res === '') {
180 res = 'true';
181 }
182 return res;
183}
184
185
186module.exports = {
187 usesScopeName,
188 normalizeName,
189 validateJS,
190 isStringOnlyCode,
191 concatChildren,
192 validate,
193 addIfMissing,
194 convertText
195};