1 | import {
|
2 | getBlock,
|
3 | getComment,
|
4 | getMainFont,
|
5 | getProp,
|
6 | getValue,
|
7 | isBasic,
|
8 | isBlock,
|
9 | isCapture,
|
10 | isComment,
|
11 | isValidCode,
|
12 | isEnd,
|
13 | isFontable,
|
14 | isGroup,
|
15 | isList,
|
16 | isProp,
|
17 | isSystemScope,
|
18 | isUserComment,
|
19 | } from './helpers.js'
|
20 | import getLoc from './get-loc.js'
|
21 | import getMeta from './get-meta.js'
|
22 | import getPropTypes from './get-prop-types.js'
|
23 | import getTags from './get-tags.js'
|
24 |
|
25 | export default (rtext, skipComments = true) => {
|
26 |
|
27 | const text = rtext.replace(/\r\n/g, '\n')
|
28 | const fonts = []
|
29 | const lines = text.split('\n').map(line => line.trim())
|
30 | const props = []
|
31 | const stack = []
|
32 | const views = []
|
33 | const warnings = []
|
34 | let lastCapture
|
35 |
|
36 | const getChildrenProxyMap = block => {
|
37 | const childrenProxyMap = {}
|
38 |
|
39 | block.children.forEach((child, i) => {
|
40 | let maybeName = child.is || child.name
|
41 | let name = maybeName
|
42 | let next = 1
|
43 | while (name in childrenProxyMap) {
|
44 | name = `${maybeName}${next}`
|
45 | next++
|
46 | }
|
47 | childrenProxyMap[name] = i
|
48 | })
|
49 |
|
50 | return Object.keys(childrenProxyMap).length === 0 ? null : childrenProxyMap
|
51 | }
|
52 |
|
53 | const lookForFonts = block => {
|
54 | if (block.properties && (isFontable(block.name) || !block.isBasic)) {
|
55 | const fontFamilyProp = block.properties.find(p => p.name === 'fontFamily')
|
56 |
|
57 | if (fontFamilyProp) {
|
58 | const fontFamily = getMainFont(fontFamilyProp.value)
|
59 | const fontWeightProp = block.properties.find(
|
60 | p => p.name === 'fontWeight'
|
61 | )
|
62 | const fontStyleProp = block.properties.find(p => p.name === 'fontStyle')
|
63 | const fontWeight = fontWeightProp
|
64 | ? fontWeightProp.value.toString()
|
65 | : '400'
|
66 |
|
67 | const fontStyle = fontStyleProp
|
68 | ? fontStyleProp.value.toString()
|
69 | : 'normal'
|
70 |
|
71 | if (
|
72 | !fonts.find(
|
73 | font =>
|
74 | font.family === fontFamily &&
|
75 | font.weight === fontWeight &&
|
76 | font.style === fontStyle
|
77 | )
|
78 | ) {
|
79 | fonts.push({
|
80 | id: `${fontFamily}-${fontWeight}${
|
81 | fontStyle === 'italic' ? '-italic' : ''
|
82 | }`,
|
83 | family: fontFamily,
|
84 | weight: fontWeight,
|
85 | style: fontStyle,
|
86 | })
|
87 | }
|
88 | }
|
89 | }
|
90 | }
|
91 |
|
92 | const end = (block, endLine) => {
|
93 | block.loc.end = {
|
94 | line: endLine,
|
95 | column: Math.max(0, lines[endLine].length - 1),
|
96 | }
|
97 |
|
98 | if (block.isGroup && !block.isBasic) {
|
99 | block.childrenProxyMap = getChildrenProxyMap(block)
|
100 | }
|
101 |
|
102 | if (!block.properties) {
|
103 | block.properties = []
|
104 | }
|
105 |
|
106 | if (stack.length === 0) {
|
107 |
|
108 | views.push(block)
|
109 | return true
|
110 | }
|
111 | return false
|
112 | }
|
113 |
|
114 | const parseBlock = (line, i) => {
|
115 | const { block: name, is } = getBlock(line)
|
116 | let shouldPushToStack = false
|
117 |
|
118 | const block = {
|
119 | type: 'Block',
|
120 | name,
|
121 | isBasic: isBasic(name),
|
122 | isGroup: false,
|
123 | loc: getLoc(i, 0),
|
124 | properties: [],
|
125 | scopes: [],
|
126 | }
|
127 |
|
128 | if (is) {
|
129 | block.is = is
|
130 |
|
131 | if (isCapture(name)) {
|
132 | if (lastCapture) {
|
133 | lastCapture.captureNext = is
|
134 | }
|
135 | lastCapture = block
|
136 | }
|
137 | }
|
138 |
|
139 | const last = stack[stack.length - 1]
|
140 | if (last) {
|
141 | if (last.isGroup) {
|
142 | if (last.isList) {
|
143 | if (block.isBasic) {
|
144 | warnings.push({
|
145 | loc: block.loc,
|
146 | type: `A basic block can't be inside a List.\nPut 1 empty line before.`,
|
147 | line,
|
148 | })
|
149 |
|
150 | shouldPushToStack = true
|
151 | } else if (last.children.length > 0) {
|
152 | warnings.push({
|
153 | loc: block.loc,
|
154 | type: `A List can only have one view inside. This block is outside of it.\nPut 1 empty line before.`,
|
155 | line,
|
156 | })
|
157 | shouldPushToStack = true
|
158 | } else {
|
159 | last.children.push(block)
|
160 | }
|
161 | } else {
|
162 | last.children.push(block)
|
163 | }
|
164 | } else {
|
165 |
|
166 | end(stack.pop(), i)
|
167 |
|
168 | if (views[0].isGroup) {
|
169 | warnings.push({
|
170 | loc: block.loc,
|
171 | type:
|
172 | lines[i - 1] === ''
|
173 | ? `Put 1 empty line before`
|
174 | : `Put 2 empty lines before`,
|
175 | line,
|
176 | })
|
177 | } else {
|
178 | warnings.push({
|
179 | loc: block.loc,
|
180 | type: `Add Vertical at the top`,
|
181 | line,
|
182 | })
|
183 | }
|
184 | shouldPushToStack = true
|
185 | }
|
186 | } else if (views.length > 0) {
|
187 |
|
188 | let newLinesBeforePreviousBlock = 1
|
189 | while (isEnd(lines[i - newLinesBeforePreviousBlock])) {
|
190 | newLinesBeforePreviousBlock++
|
191 | }
|
192 |
|
193 | const help = []
|
194 | if (!views[0].isGroup) {
|
195 | help.push(`Add Vertical at the top`)
|
196 | }
|
197 | if (newLinesBeforePreviousBlock > 2) {
|
198 | const linesToRemove = newLinesBeforePreviousBlock - 2
|
199 | help.push(
|
200 | `remove ${linesToRemove} empty line${
|
201 | linesToRemove > 1 ? 's' : ''
|
202 | } before`
|
203 | )
|
204 | }
|
205 | warnings.push({
|
206 | loc: block.loc,
|
207 | type: help.join(', '),
|
208 | line,
|
209 | })
|
210 | }
|
211 |
|
212 | if (isGroup(name)) {
|
213 | block.isGroup = true
|
214 | block.isList = isList(name)
|
215 | block.children = []
|
216 |
|
217 | shouldPushToStack = true
|
218 | }
|
219 |
|
220 | if (shouldPushToStack && name === 'FakeProps') {
|
221 | warnings.push({
|
222 | type:
|
223 | 'FakeProps needs to be outside of any top block. Add new lines before it.',
|
224 | loc: block.loc,
|
225 | line,
|
226 | })
|
227 | }
|
228 |
|
229 | if (shouldPushToStack || stack.length === 0) {
|
230 | stack.push(block)
|
231 | }
|
232 |
|
233 | parseProps(i, block)
|
234 | lookForFonts(block)
|
235 | }
|
236 |
|
237 | const parseProps = (i, block) => {
|
238 | let endOfBlockIndex = i
|
239 | while (
|
240 | endOfBlockIndex < lines.length - 1 &&
|
241 | !isBlock(lines[endOfBlockIndex + 1])
|
242 | ) {
|
243 | endOfBlockIndex++
|
244 | }
|
245 |
|
246 | const properties = []
|
247 | const scopes = []
|
248 | let scope
|
249 | let inScope = false
|
250 |
|
251 | for (let j = i; j <= endOfBlockIndex; j++) {
|
252 | const line = lines[j]
|
253 |
|
254 | let propNode = null
|
255 |
|
256 | if (isProp(line)) {
|
257 | const [name, value] = getProp(line)
|
258 | const loc = getLoc(j, line.indexOf(name), line.length - 1)
|
259 | const tags = getTags(name, value)
|
260 |
|
261 | if (tags.code) {
|
262 | props.push({ type: block.name, name, value })
|
263 | }
|
264 |
|
265 | if (
|
266 | (tags.code || tags.shouldBeCode) &&
|
267 | name !== 'when' &&
|
268 | !isValidCode(value)
|
269 | ) {
|
270 | warnings.push({
|
271 | loc,
|
272 | type: 'The code you used in the props value is invalid',
|
273 | line,
|
274 | })
|
275 | }
|
276 |
|
277 | if (tags.style && tags.code) {
|
278 | block.maybeAnimated = true
|
279 | }
|
280 |
|
281 | if (name === 'when') {
|
282 | const isSystem = isSystemScope(value)
|
283 |
|
284 | if (value === '') {
|
285 | warnings.push({
|
286 | loc,
|
287 | type: 'This when has no props, add some condition to it',
|
288 | line,
|
289 | })
|
290 | } else if (value === 'props') {
|
291 | warnings.push({
|
292 | loc,
|
293 | type: `You can't use the props shorthand in a when`,
|
294 | line,
|
295 | })
|
296 | } else if (
|
297 | !isSystem &&
|
298 | !isValidCode(value) &&
|
299 | block.name !== 'FakeProps'
|
300 | ) {
|
301 | warnings.push({
|
302 | loc,
|
303 | type: 'The code you used in the props value is invalid',
|
304 | line,
|
305 | })
|
306 | }
|
307 |
|
308 | tags.scope = value
|
309 | inScope = value
|
310 | scope = { isSystem, value, properties: [] }
|
311 | scopes.push(scope)
|
312 | }
|
313 |
|
314 | propNode = {
|
315 | type: 'Property',
|
316 | loc,
|
317 | name,
|
318 | tags,
|
319 | meta: getMeta(value, line, j),
|
320 | value: getValue(value),
|
321 | }
|
322 | } else if (isComment(line) && !skipComments) {
|
323 | let [value] = getComment(line)
|
324 |
|
325 | const userComment = isUserComment(line)
|
326 | if (userComment) {
|
327 | value = getComment(value)
|
328 | }
|
329 |
|
330 | propNode = {
|
331 | type: 'Property',
|
332 | loc: getLoc(j, 0, line.length - 1),
|
333 | value,
|
334 | tags: { comment: true, userComment },
|
335 | }
|
336 | }
|
337 |
|
338 | if (propNode) {
|
339 | block.loc.end = propNode.loc.end
|
340 |
|
341 | if (inScope) {
|
342 | if (
|
343 | propNode.name !== 'when' &&
|
344 | !propNode.tags.comment &&
|
345 | !properties.some(baseProp => baseProp.name === propNode.name)
|
346 | ) {
|
347 | warnings.push({
|
348 | loc: propNode.loc,
|
349 | type: `You're missing a base prop for ${
|
350 | propNode.name
|
351 | }. Add it before all whens on the block.`,
|
352 | line,
|
353 | })
|
354 | }
|
355 |
|
356 | scope.properties.push(propNode)
|
357 | } else {
|
358 | properties.push(propNode)
|
359 | }
|
360 | }
|
361 | }
|
362 |
|
363 | block.properties = properties
|
364 | block.scopes = scopes
|
365 | }
|
366 |
|
367 | lines.forEach((line, i) => {
|
368 | if (isBlock(line)) {
|
369 | parseBlock(line, i)
|
370 | } else if (isEnd(line) && stack.length > 0) {
|
371 | end(stack.pop(), i)
|
372 | }
|
373 | })
|
374 |
|
375 | if (stack.length > 0) {
|
376 | while (!end(stack.pop(), lines.length - 1)) {}
|
377 | }
|
378 |
|
379 | return {
|
380 | fonts,
|
381 | props: getPropTypes(props),
|
382 | views,
|
383 | warnings,
|
384 | }
|
385 | }
|