@teleporthq/teleport-uidl-validator 0.18.0
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.
- package/LICENSE +21 -0
- package/README.md +14 -0
- package/__tests__/parser/componennt-with-referenced-styles.json +111 -0
- package/__tests__/parser/component-parsing.ts +116 -0
- package/__tests__/parser/component-referenced.ts +50 -0
- package/__tests__/parser/component-with-primitive-values.json +61 -0
- package/__tests__/parser/component-with-proper-values.json +97 -0
- package/__tests__/parser/component-with-reusalble-styles.json +117 -0
- package/__tests__/validator/component-element-with-empty-name.json +36 -0
- package/__tests__/validator/component-invalid-sample.json +184 -0
- package/__tests__/validator/component-uidl-with-event-modifier-undefined.json +45 -0
- package/__tests__/validator/component-uidl-with-null-undefined.json +24 -0
- package/__tests__/validator/index.ts +216 -0
- package/__tests__/validator/old-project-invalid-sample.json +317 -0
- package/__tests__/validator/project-invalid-sample-no-route.json +667 -0
- package/__tests__/validator/project-invalid-sample.json +671 -0
- package/__tests__/validator/project-sample.json +667 -0
- package/dist/cjs/decoders/component-decoder.d.ts +4 -0
- package/dist/cjs/decoders/component-decoder.js +30 -0
- package/dist/cjs/decoders/component-decoder.js.map +1 -0
- package/dist/cjs/decoders/custom-combinators.d.ts +8 -0
- package/dist/cjs/decoders/custom-combinators.js +59 -0
- package/dist/cjs/decoders/custom-combinators.js.map +1 -0
- package/dist/cjs/decoders/index.d.ts +2 -0
- package/dist/cjs/decoders/index.js +9 -0
- package/dist/cjs/decoders/index.js.map +1 -0
- package/dist/cjs/decoders/project-decoder.d.ts +5 -0
- package/dist/cjs/decoders/project-decoder.js +38 -0
- package/dist/cjs/decoders/project-decoder.js.map +1 -0
- package/dist/cjs/decoders/utils.d.ts +59 -0
- package/dist/cjs/decoders/utils.js +305 -0
- package/dist/cjs/decoders/utils.js.map +1 -0
- package/dist/cjs/index.d.ts +7 -0
- package/dist/cjs/index.js +38 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/parser/index.d.ts +10 -0
- package/dist/cjs/parser/index.js +156 -0
- package/dist/cjs/parser/index.js.map +1 -0
- package/dist/cjs/tsconfig.tsbuildinfo +1 -0
- package/dist/cjs/validator/index.d.ts +15 -0
- package/dist/cjs/validator/index.js +94 -0
- package/dist/cjs/validator/index.js.map +1 -0
- package/dist/cjs/validator/utils.d.ts +16 -0
- package/dist/cjs/validator/utils.js +373 -0
- package/dist/cjs/validator/utils.js.map +1 -0
- package/dist/esm/decoders/component-decoder.d.ts +4 -0
- package/dist/esm/decoders/component-decoder.js +27 -0
- package/dist/esm/decoders/component-decoder.js.map +1 -0
- package/dist/esm/decoders/custom-combinators.d.ts +8 -0
- package/dist/esm/decoders/custom-combinators.js +56 -0
- package/dist/esm/decoders/custom-combinators.js.map +1 -0
- package/dist/esm/decoders/index.d.ts +2 -0
- package/dist/esm/decoders/index.js +3 -0
- package/dist/esm/decoders/index.js.map +1 -0
- package/dist/esm/decoders/project-decoder.d.ts +5 -0
- package/dist/esm/decoders/project-decoder.js +35 -0
- package/dist/esm/decoders/project-decoder.js.map +1 -0
- package/dist/esm/decoders/utils.d.ts +59 -0
- package/dist/esm/decoders/utils.js +301 -0
- package/dist/esm/decoders/utils.js.map +1 -0
- package/dist/esm/index.d.ts +7 -0
- package/dist/esm/index.js +8 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/parser/index.d.ts +10 -0
- package/dist/esm/parser/index.js +151 -0
- package/dist/esm/parser/index.js.map +1 -0
- package/dist/esm/tsconfig.tsbuildinfo +1 -0
- package/dist/esm/validator/index.d.ts +15 -0
- package/dist/esm/validator/index.js +73 -0
- package/dist/esm/validator/index.js.map +1 -0
- package/dist/esm/validator/utils.d.ts +16 -0
- package/dist/esm/validator/utils.js +359 -0
- package/dist/esm/validator/utils.js.map +1 -0
- package/package.json +32 -0
- package/src/decoders/component-decoder.ts +48 -0
- package/src/decoders/custom-combinators.ts +59 -0
- package/src/decoders/index.ts +2 -0
- package/src/decoders/project-decoder.ts +40 -0
- package/src/decoders/utils.ts +571 -0
- package/src/index.ts +9 -0
- package/src/parser/index.ts +227 -0
- package/src/validator/index.ts +105 -0
- package/src/validator/utils.ts +463 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
import { UIDLUtils } from '@teleporthq/teleport-shared'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
ProjectUIDL,
|
|
5
|
+
UIDLElement,
|
|
6
|
+
ComponentUIDL,
|
|
7
|
+
UIDLNode,
|
|
8
|
+
UIDLStyleSetDefinition,
|
|
9
|
+
UIDLStaticValue,
|
|
10
|
+
UIDLExternalDependency,
|
|
11
|
+
UIDLElementNodeProjectReferencedStyle,
|
|
12
|
+
UIDLStyleValue,
|
|
13
|
+
UIDLElementNodeInlineReferencedStyle,
|
|
14
|
+
UIDLReferencedStyles,
|
|
15
|
+
UIDLStyleSetTokenReference,
|
|
16
|
+
ComponentValidationError,
|
|
17
|
+
} from '@teleporthq/teleport-types'
|
|
18
|
+
|
|
19
|
+
// Prop definitions and state definitions should have different keys
|
|
20
|
+
export const checkForDuplicateDefinitions = (input: ComponentUIDL) => {
|
|
21
|
+
const props = Object.keys(input.propDefinitions || {})
|
|
22
|
+
const states = Object.keys(input.stateDefinitions || {})
|
|
23
|
+
const imports = Object.keys(input.importDefinitions || {})
|
|
24
|
+
|
|
25
|
+
props
|
|
26
|
+
.filter((x) => states.includes(x) || imports.includes(x))
|
|
27
|
+
.forEach((duplicate) =>
|
|
28
|
+
console.warn(
|
|
29
|
+
`\n"${duplicate}" is defined both as a prop and as a state. If you are using VUE Code Generators this can cause bad behavior.`
|
|
30
|
+
)
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// In "repeat" node:
|
|
35
|
+
// If index is used, "useIndex" must be declared in "meta"
|
|
36
|
+
// If custom local variable is used, it's name must be specified inside "meta" as "iteratorName"
|
|
37
|
+
export const checkForLocalVariables = (input: ComponentUIDL) => {
|
|
38
|
+
const errors: string[] = []
|
|
39
|
+
|
|
40
|
+
UIDLUtils.traverseRepeats(input.node, (repeatContent) => {
|
|
41
|
+
UIDLUtils.traverseNodes(repeatContent.node, (childNode) => {
|
|
42
|
+
if (childNode.type === 'dynamic' && childNode.content.referenceType === 'local') {
|
|
43
|
+
if (childNode.content.id === 'index') {
|
|
44
|
+
if (!repeatContent.meta.useIndex) {
|
|
45
|
+
const errorMsg = `\nIndex variable is used but the "useIndex" meta information is false.`
|
|
46
|
+
errors.push(errorMsg)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// we are dealing with local index here
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!validLocalVariableUsage(childNode.content.id, repeatContent.meta.iteratorName)) {
|
|
54
|
+
const errorMsg = `\n"${childNode.content.id}" is used in the "repeat" structure but the iterator name has this value: "${repeatContent.meta.iteratorName}"`
|
|
55
|
+
errors.push(errorMsg)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
})
|
|
60
|
+
return errors
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const validLocalVariableUsage = (dynamicId: string, repeatIteratorName: string) => {
|
|
64
|
+
const iteratorName = repeatIteratorName || 'item'
|
|
65
|
+
|
|
66
|
+
if (!dynamicId.includes('.')) {
|
|
67
|
+
return dynamicId === iteratorName
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const dynamicIdRoot = dynamicId.split('.')[0]
|
|
71
|
+
return dynamicIdRoot === iteratorName
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* All referenced props, states and importRefs should be previously defined in the
|
|
75
|
+
"propDefinitions" and "stateDefinitions" and "importDefinitions" sections
|
|
76
|
+
If props or states are defined and not used, a warning witll be displayed */
|
|
77
|
+
export const checkDynamicDefinitions = (input: Record<string, unknown>) => {
|
|
78
|
+
const propKeys = Object.keys(input.propDefinitions || {})
|
|
79
|
+
const stateKeys = Object.keys(input.stateDefinitions || {})
|
|
80
|
+
let importKeys = Object.keys(input.importDefinitions || {})
|
|
81
|
+
const componentStyleSetKyes = Object.keys(input.styleSetDefinitions || {})
|
|
82
|
+
|
|
83
|
+
const importDefinitions: { [key: string]: UIDLExternalDependency } = (input?.importDefinitions ??
|
|
84
|
+
{}) as unknown as { [key: string]: UIDLExternalDependency }
|
|
85
|
+
|
|
86
|
+
if (Object.keys(importKeys).length > 0) {
|
|
87
|
+
importKeys = importKeys.reduce((acc, importRef) => {
|
|
88
|
+
if (importDefinitions[importRef]?.meta?.importJustPath) {
|
|
89
|
+
return acc
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
acc.push(importRef)
|
|
93
|
+
return acc
|
|
94
|
+
}, [])
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const usedPropKeys: string[] = []
|
|
98
|
+
const usedStateKeys: string[] = []
|
|
99
|
+
const usedImportKeys: string[] = []
|
|
100
|
+
const errors: string[] = []
|
|
101
|
+
|
|
102
|
+
UIDLUtils.traverseNodes(input.node as UIDLNode, (node) => {
|
|
103
|
+
if (node.type === 'element') {
|
|
104
|
+
Object.keys(node.content?.events || {}).forEach((eventKey) => {
|
|
105
|
+
node.content.events[eventKey].forEach((event) => {
|
|
106
|
+
if (event.type === 'stateChange' && !stateKeys.includes(event.modifies)) {
|
|
107
|
+
const errorMsg = `"${event.modifies}" is used in events, but not defined. Please add it in stateDefinitions`
|
|
108
|
+
errors.push(errorMsg)
|
|
109
|
+
return
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (event.type === 'propCall' && !propKeys.includes(event.calls)) {
|
|
113
|
+
errors.push(
|
|
114
|
+
`"${event.calls}" is used in events, but missing from propDefinitons. Please add it in propDefinitions `
|
|
115
|
+
)
|
|
116
|
+
return
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
const dynamicVariants: string[] = []
|
|
122
|
+
Object.values(node.content?.referencedStyles || {}).forEach((styleRef) => {
|
|
123
|
+
if (
|
|
124
|
+
styleRef.content.mapType === 'component-referenced' &&
|
|
125
|
+
styleRef.content.content.type === 'dynamic'
|
|
126
|
+
) {
|
|
127
|
+
if (styleRef.content.content.content.referenceType === 'prop') {
|
|
128
|
+
const referencedProp = styleRef.content.content.content.id
|
|
129
|
+
if (!dynamicPathExistsInDefinitions(referencedProp, propKeys)) {
|
|
130
|
+
const errorMsg = `"${referencedProp}" is used but not defined. Please add it in propDefinitions`
|
|
131
|
+
errors.push(errorMsg)
|
|
132
|
+
return
|
|
133
|
+
}
|
|
134
|
+
dynamicVariants.push(referencedProp)
|
|
135
|
+
usedPropKeys.push(referencedProp)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (styleRef.content.content.content.referenceType === 'comp') {
|
|
139
|
+
const compStyleRefId = styleRef.content.content.content.id
|
|
140
|
+
|
|
141
|
+
if (!componentStyleSetKyes.includes(compStyleRefId)) {
|
|
142
|
+
errors.push(
|
|
143
|
+
`${compStyleRefId} is used, but not defined in Component Style Sheet in ${input.name}. Please add it in StyleSetDefinitions of the component`
|
|
144
|
+
)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
if (dynamicVariants.length > 1) {
|
|
151
|
+
errors.push(`Node ${
|
|
152
|
+
node.content?.name || node.content?.key
|
|
153
|
+
} is using multiple dynamic variants using propDefinitions.
|
|
154
|
+
We can have only one dynamic variant at once`)
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (node.type === 'dynamic' && node.content.referenceType === 'prop') {
|
|
159
|
+
if (!dynamicPathExistsInDefinitions(node.content.id, propKeys)) {
|
|
160
|
+
const errorMsg = `"${node.content.id}" is used but not defined. Please add it in propDefinitions`
|
|
161
|
+
errors.push(errorMsg)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// for member expression we check the root
|
|
165
|
+
// if value has no `.` it will be checked as it is
|
|
166
|
+
const dynamicIdRoot = node.content.id.split('.')[0]
|
|
167
|
+
usedPropKeys.push(dynamicIdRoot)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (node.type === 'dynamic' && node.content.referenceType === 'state') {
|
|
171
|
+
if (!dynamicPathExistsInDefinitions(node.content.id, stateKeys)) {
|
|
172
|
+
const errorMsg = `\n"${node.content.id}" is used but not defined. Please add it in stateDefinitions`
|
|
173
|
+
errors.push(errorMsg)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// for member expression we check the root
|
|
177
|
+
// if value has no `.` it will be checked as it is
|
|
178
|
+
const dynamicIdRoot = node.content.id.split('.')[0]
|
|
179
|
+
usedStateKeys.push(dynamicIdRoot)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (node.type === 'import') {
|
|
183
|
+
if (!dynamicPathExistsInDefinitions(node.content.id, importKeys)) {
|
|
184
|
+
const errorMsg = `\n"${node.content.id}" is used but not defined. Please add it in importDefinitions`
|
|
185
|
+
errors.push(errorMsg)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// for member expression we check the root
|
|
189
|
+
// if value has no `.` it will be checked as it is
|
|
190
|
+
const dynamicIdRoot = node.content.id.split('.')[0]
|
|
191
|
+
usedImportKeys.push(dynamicIdRoot)
|
|
192
|
+
}
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
propKeys
|
|
196
|
+
.filter((x) => !usedPropKeys.includes(x))
|
|
197
|
+
.forEach((diff) =>
|
|
198
|
+
console.warn(`"${diff}" is defined in propDefinitions but it is not used in the UIDL.`)
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
stateKeys
|
|
202
|
+
.filter((x) => !usedStateKeys.includes(x))
|
|
203
|
+
.forEach((diff) =>
|
|
204
|
+
console.warn(`"${diff}" is defined in stateDefinitions but it is not used in the UIDL.`)
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
importKeys
|
|
208
|
+
.filter((x) => !usedImportKeys.includes(x))
|
|
209
|
+
.forEach((diff) =>
|
|
210
|
+
console.warn(`"${diff}" is defined in importDefinitions but it is not used in the UIDL.`)
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
return errors
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const dynamicPathExistsInDefinitions = (path: string, defKeys: string[]) => {
|
|
217
|
+
if (!path.includes('.')) {
|
|
218
|
+
// prop/state is a scalar value, not a dot notation
|
|
219
|
+
return defKeys.includes(path)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// TODO: Expand validation logic to check if the path exists on the prop/state definition
|
|
223
|
+
// ex: if prop reference is `user.name`, we should check that prop type is object and has a valid field name
|
|
224
|
+
const rootIdentifier = path.split('.')[0]
|
|
225
|
+
return defKeys.includes(rootIdentifier)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// A projectUIDL must contain "route" key
|
|
229
|
+
export const checkRouteDefinition = (input: ProjectUIDL) => {
|
|
230
|
+
const errors = []
|
|
231
|
+
|
|
232
|
+
const keys = Object.keys(input.root.stateDefinitions || {})
|
|
233
|
+
if (!keys.includes('route')) {
|
|
234
|
+
const errorMsg = 'Route is not defined in stateDefinitions'
|
|
235
|
+
errors.push(errorMsg)
|
|
236
|
+
}
|
|
237
|
+
return errors
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// All referenced components inside of the projectUIDL should be defined
|
|
241
|
+
// in the components section and all the project-referenced styles and tokens
|
|
242
|
+
export const checkComponentExistenceAndReferences = (input: ProjectUIDL) => {
|
|
243
|
+
const errors: string[] = []
|
|
244
|
+
const components = Object.keys(input.components || {})
|
|
245
|
+
const styleSetDefinitions = Object.keys(input.root?.styleSetDefinitions || {})
|
|
246
|
+
const tokens: string[] = Object.keys(input.root?.designLanguage?.tokens || {})
|
|
247
|
+
const nodesToParse = [
|
|
248
|
+
input.root.node,
|
|
249
|
+
...Object.values(input.components || {}).map((component) => component.node),
|
|
250
|
+
]
|
|
251
|
+
let usedReferencedStyles: string[] = []
|
|
252
|
+
|
|
253
|
+
if (input.root?.styleSetDefinitions) {
|
|
254
|
+
Object.values(input.root.styleSetDefinitions || {}).forEach((style) => {
|
|
255
|
+
const { content, conditions = [] } = style
|
|
256
|
+
errors.push(...checkForTokensInstyles(content, tokens))
|
|
257
|
+
conditions.forEach((condition) => {
|
|
258
|
+
if (condition?.content) {
|
|
259
|
+
errors.push(...checkForTokensInstyles(condition.content, tokens))
|
|
260
|
+
}
|
|
261
|
+
})
|
|
262
|
+
})
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
nodesToParse.forEach((node) => {
|
|
266
|
+
UIDLUtils.traverseElements(node, (element) => {
|
|
267
|
+
/* Checking for project-referenced styles */
|
|
268
|
+
if (element?.referencedStyles) {
|
|
269
|
+
const { errorsInRferences, usedStyleRefrences } = checkForReferencedStylesUsed(
|
|
270
|
+
element.referencedStyles,
|
|
271
|
+
styleSetDefinitions,
|
|
272
|
+
tokens
|
|
273
|
+
)
|
|
274
|
+
errors.push(...errorsInRferences)
|
|
275
|
+
usedReferencedStyles = [...usedReferencedStyles, ...usedStyleRefrences]
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/* Checking for token references used in styles */
|
|
279
|
+
if (element?.style) {
|
|
280
|
+
errors.push(...checkForTokensInstyles(element.style, tokens))
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (
|
|
284
|
+
element.dependency &&
|
|
285
|
+
element.dependency.type === 'local' &&
|
|
286
|
+
!components.includes(element.semanticType)
|
|
287
|
+
) {
|
|
288
|
+
const errorMsg = `\nThe component "${element.semanticType}" is not defined in the UIDL's component section.`
|
|
289
|
+
errors.push(errorMsg)
|
|
290
|
+
}
|
|
291
|
+
})
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
styleSetDefinitions.forEach((key) => {
|
|
295
|
+
if (!usedReferencedStyles.includes(key)) {
|
|
296
|
+
console.warn(`${key} styleSet is defined but not used in the project.`)
|
|
297
|
+
}
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
return errors
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const checkForReferencedStylesUsed = (
|
|
304
|
+
referencedStyles: UIDLReferencedStyles,
|
|
305
|
+
styleSetDefinitions: string[],
|
|
306
|
+
tokens: string[]
|
|
307
|
+
) => {
|
|
308
|
+
const errorsInRferences: string[] = []
|
|
309
|
+
const usedStyleRefrences: string[] = []
|
|
310
|
+
Object.values(referencedStyles || {}).forEach((styleRef) => {
|
|
311
|
+
const { mapType } = styleRef.content
|
|
312
|
+
|
|
313
|
+
if (mapType === 'inlined') {
|
|
314
|
+
errorsInRferences.push(
|
|
315
|
+
...checkForTokensInstyles(
|
|
316
|
+
(styleRef as UIDLElementNodeInlineReferencedStyle).content.styles,
|
|
317
|
+
tokens
|
|
318
|
+
)
|
|
319
|
+
)
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (mapType === 'project-referenced') {
|
|
323
|
+
const { referenceId } = (styleRef as UIDLElementNodeProjectReferencedStyle).content
|
|
324
|
+
usedStyleRefrences.push(referenceId)
|
|
325
|
+
if (!styleSetDefinitions.includes(referenceId)) {
|
|
326
|
+
errorsInRferences.push(
|
|
327
|
+
`\n ${referenceId} is missing from the styleSetDefinitions, please check the reference id.`
|
|
328
|
+
)
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
})
|
|
332
|
+
return {
|
|
333
|
+
errorsInRferences,
|
|
334
|
+
usedStyleRefrences,
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const checkForTokensInstyles = (styles: Record<string, UIDLStyleValue>, tokens: string[]) => {
|
|
339
|
+
const errors: string[] = []
|
|
340
|
+
Object.values(styles || {}).forEach((style: UIDLStyleValue) => {
|
|
341
|
+
if (
|
|
342
|
+
style.type === 'dynamic' &&
|
|
343
|
+
style.content.referenceType === 'token' &&
|
|
344
|
+
!tokens.includes(style.content.id)
|
|
345
|
+
) {
|
|
346
|
+
errors.push(`\nToken ${style.content.id} is missing from the project UIDL.`)
|
|
347
|
+
}
|
|
348
|
+
})
|
|
349
|
+
return errors
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// All components should have the same key as the value of their name key
|
|
353
|
+
// Example:
|
|
354
|
+
// "components": {
|
|
355
|
+
// "OneComponent": {
|
|
356
|
+
// "name": "OneComponent",
|
|
357
|
+
// ..
|
|
358
|
+
// }
|
|
359
|
+
// }
|
|
360
|
+
export const checkComponentNaming = (input: ProjectUIDL) => {
|
|
361
|
+
const errors: string[] = []
|
|
362
|
+
const namesUsed = Object.keys(input.components || {})
|
|
363
|
+
|
|
364
|
+
const diffs = namesUsed.filter((name) => input.components[name].name !== name)
|
|
365
|
+
|
|
366
|
+
if (diffs.length > 0) {
|
|
367
|
+
const errorMsg = `\nThe following components have different name than their key: ${diffs}`
|
|
368
|
+
errors.push(errorMsg)
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
return errors
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
export const checkProjectStyleSet = (input: ProjectUIDL) => {
|
|
375
|
+
const errors: string[] = []
|
|
376
|
+
const styleSet = input.root.styleSetDefinitions
|
|
377
|
+
if (styleSet) {
|
|
378
|
+
Object.values(styleSet).forEach((styleSetObj: UIDLStyleSetDefinition) => {
|
|
379
|
+
const { content, conditions = [] } = styleSetObj
|
|
380
|
+
|
|
381
|
+
Object.values(conditions).forEach((style) => {
|
|
382
|
+
errors.push(...checStylekContentForErrors(style.content))
|
|
383
|
+
})
|
|
384
|
+
|
|
385
|
+
errors.push(...checStylekContentForErrors(content))
|
|
386
|
+
})
|
|
387
|
+
}
|
|
388
|
+
return errors
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
export const checStylekContentForErrors = (
|
|
392
|
+
content: Record<string, UIDLStaticValue | UIDLStyleSetTokenReference>
|
|
393
|
+
) => {
|
|
394
|
+
const errors: string[] = []
|
|
395
|
+
Object.values(content).forEach((styleContent) => {
|
|
396
|
+
if (styleContent.type === 'dynamic' && styleContent.content.referenceType !== 'token') {
|
|
397
|
+
errors.push(`Dynamic nodes in styleSetDefinitions supports only tokens`)
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
if (
|
|
401
|
+
styleContent.type === 'static' &&
|
|
402
|
+
typeof styleContent.content !== 'string' &&
|
|
403
|
+
typeof styleContent.content !== 'number'
|
|
404
|
+
) {
|
|
405
|
+
errors.push(
|
|
406
|
+
`Project Style sheet / styleSetDefinitions only support styles with static
|
|
407
|
+
content and dynamic tokens, received ${styleContent}`
|
|
408
|
+
)
|
|
409
|
+
}
|
|
410
|
+
})
|
|
411
|
+
return errors
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// The "root" node should contain only elements of type "conditional"
|
|
415
|
+
export const checkRootComponent = (input: ProjectUIDL) => {
|
|
416
|
+
const errors = []
|
|
417
|
+
const routeNaming: string[] = []
|
|
418
|
+
const rootNode = input.root.node.content as UIDLElement
|
|
419
|
+
rootNode.children.forEach((child) => {
|
|
420
|
+
if (child.type !== 'conditional') {
|
|
421
|
+
const errorMsg = `\nRoot Node contains elements of type "${child.type}". It should contain only elements of type "conditional"`
|
|
422
|
+
errors.push(errorMsg)
|
|
423
|
+
} else {
|
|
424
|
+
routeNaming.push(child.content.value.toString())
|
|
425
|
+
}
|
|
426
|
+
})
|
|
427
|
+
|
|
428
|
+
const routeValues = input.root.stateDefinitions.route.values
|
|
429
|
+
if (!routeValues || routeValues.length <= 0) {
|
|
430
|
+
errors.push(
|
|
431
|
+
'\nThe `route` state definition from the root node does not contain the possible route values'
|
|
432
|
+
)
|
|
433
|
+
} else {
|
|
434
|
+
input.root.stateDefinitions.route.values
|
|
435
|
+
.filter((route) => !routeNaming.includes(route.value.toString()))
|
|
436
|
+
.forEach((route) => {
|
|
437
|
+
const errorMsg = `\nRoot Node contains a route that don't have a specified state: ${route.value}.`
|
|
438
|
+
errors.push(errorMsg)
|
|
439
|
+
})
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
return errors
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// The errors should be displayed in a human-readeable way
|
|
446
|
+
export const formatErrors = (errors: Array<{ kind: string; at: string; message: string }>) => {
|
|
447
|
+
const listOfErrors: string[] = []
|
|
448
|
+
errors.forEach((error) => {
|
|
449
|
+
const message = `\n - Path ${error.at}: ${error.message}. \n is a ${error.kind} \n`
|
|
450
|
+
listOfErrors.push(message)
|
|
451
|
+
})
|
|
452
|
+
|
|
453
|
+
return `UIDL Format Validation Error. Please check the following: ${listOfErrors}`
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
export const validateNulls = (uidl: Record<string, unknown>) => {
|
|
457
|
+
return JSON.parse(JSON.stringify(uidl), (key, value) => {
|
|
458
|
+
if (value === undefined || value == null) {
|
|
459
|
+
throw new ComponentValidationError(`Validation error, Received ${value} at ${key}`)
|
|
460
|
+
}
|
|
461
|
+
return value
|
|
462
|
+
})
|
|
463
|
+
}
|