UNPKG

9.37 kBJavaScriptView Raw
1import {
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'
20import getLoc from './get-loc.js'
21import getMeta from './get-meta.js'
22import getPropTypes from './get-prop-types.js'
23import getTags from './get-tags.js'
24
25export default (rtext, skipComments = true) => {
26 // convert crlf to lf
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 // if we're the last block on the stack, then this is the view!
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 // the block is inside a block that isn't a group
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 // the block is outside the top level block
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}