@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,227 @@
|
|
|
1
|
+
import { UIDLUtils } from '@teleporthq/teleport-shared'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
UIDLDynamicReference,
|
|
5
|
+
ComponentUIDL,
|
|
6
|
+
ProjectUIDL,
|
|
7
|
+
UIDLNode,
|
|
8
|
+
UIDLConditionalNode,
|
|
9
|
+
UIDLRepeatNode,
|
|
10
|
+
UIDLSlotNode,
|
|
11
|
+
UIDLElementNode,
|
|
12
|
+
UIDLStaticValue,
|
|
13
|
+
UIDLStyleSetConditions,
|
|
14
|
+
UIDLDesignTokens,
|
|
15
|
+
ParserError,
|
|
16
|
+
} from '@teleporthq/teleport-types'
|
|
17
|
+
|
|
18
|
+
interface ParseComponentJSONParams {
|
|
19
|
+
noClone?: boolean
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const parseComponentJSON = (
|
|
23
|
+
input: Record<string, unknown>,
|
|
24
|
+
params: ParseComponentJSONParams = {}
|
|
25
|
+
): ComponentUIDL => {
|
|
26
|
+
const safeInput = params.noClone ? input : UIDLUtils.cloneObject(input)
|
|
27
|
+
|
|
28
|
+
if (input?.styleSetDefinitions) {
|
|
29
|
+
const { styleSetDefinitions } = safeInput
|
|
30
|
+
|
|
31
|
+
Object.values(styleSetDefinitions).forEach((styleRef) => {
|
|
32
|
+
const { conditions = [] } = styleRef
|
|
33
|
+
styleRef.content = UIDLUtils.transformStylesAssignmentsToJson(styleRef.content)
|
|
34
|
+
if (conditions.length > 0) {
|
|
35
|
+
conditions.forEach((style: UIDLStyleSetConditions) => {
|
|
36
|
+
style.content = UIDLUtils.transformStylesAssignmentsToJson(style.content) as Record<
|
|
37
|
+
string,
|
|
38
|
+
UIDLStaticValue
|
|
39
|
+
>
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const node = safeInput.node as Record<string, unknown>
|
|
46
|
+
const result: ComponentUIDL = {
|
|
47
|
+
...(safeInput as unknown as ComponentUIDL),
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// other parsers for other sections of the component here
|
|
51
|
+
result.node = parseComponentNode(node) as UIDLElementNode
|
|
52
|
+
|
|
53
|
+
return result
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
interface ParseProjectJSONParams {
|
|
57
|
+
noClone?: boolean
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export const parseProjectJSON = (
|
|
61
|
+
input: Record<string, unknown>,
|
|
62
|
+
params: ParseProjectJSONParams = {}
|
|
63
|
+
): ProjectUIDL => {
|
|
64
|
+
const safeInput = params.noClone ? input : UIDLUtils.cloneObject(input)
|
|
65
|
+
const root = safeInput.root as Record<string, unknown>
|
|
66
|
+
const result = {
|
|
67
|
+
...(safeInput as unknown as ProjectUIDL),
|
|
68
|
+
}
|
|
69
|
+
result.root = parseComponentJSON(root, { noClone: true })
|
|
70
|
+
|
|
71
|
+
if (result.root?.designLanguage) {
|
|
72
|
+
const { tokens = {} } = result.root.designLanguage
|
|
73
|
+
|
|
74
|
+
result.root.designLanguage.tokens = Object.keys(tokens).reduce(
|
|
75
|
+
(acc: UIDLDesignTokens, tokenId: string) => {
|
|
76
|
+
const token = tokens[tokenId]
|
|
77
|
+
if (typeof token === 'string' || typeof token === 'number') {
|
|
78
|
+
acc[tokenId] = {
|
|
79
|
+
type: 'static',
|
|
80
|
+
content: token,
|
|
81
|
+
}
|
|
82
|
+
} else {
|
|
83
|
+
acc[tokenId] = token
|
|
84
|
+
}
|
|
85
|
+
return acc
|
|
86
|
+
},
|
|
87
|
+
{}
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (result.components) {
|
|
92
|
+
result.components = Object.keys(result.components).reduce(
|
|
93
|
+
(parsedComponnets: Record<string, ComponentUIDL>, key) => {
|
|
94
|
+
parsedComponnets[key] = parseComponentJSON(
|
|
95
|
+
result.components[key] as unknown as Record<string, unknown>
|
|
96
|
+
)
|
|
97
|
+
return parsedComponnets
|
|
98
|
+
},
|
|
99
|
+
{}
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return result
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const parseComponentNode = (node: Record<string, unknown>): UIDLNode => {
|
|
107
|
+
switch ((node as unknown as UIDLNode).type) {
|
|
108
|
+
case 'element':
|
|
109
|
+
const elementContent = node.content as Record<string, unknown>
|
|
110
|
+
|
|
111
|
+
if (elementContent?.referencedStyles) {
|
|
112
|
+
Object.values(elementContent.referencedStyles).forEach((styleRef) => {
|
|
113
|
+
const { content } = styleRef
|
|
114
|
+
|
|
115
|
+
switch (content.mapType) {
|
|
116
|
+
case 'inlined': {
|
|
117
|
+
content.styles = UIDLUtils.transformStylesAssignmentsToJson(
|
|
118
|
+
content.styles as Record<string, string>
|
|
119
|
+
)
|
|
120
|
+
break
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
case 'project-referenced':
|
|
124
|
+
break
|
|
125
|
+
|
|
126
|
+
case 'component-referenced': {
|
|
127
|
+
if (['string', 'number'].includes(typeof styleRef.content.content)) {
|
|
128
|
+
styleRef.content.content = {
|
|
129
|
+
type: 'static',
|
|
130
|
+
content: styleRef.content.content,
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
break
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
default: {
|
|
137
|
+
throw new ParserError(
|
|
138
|
+
`Un-expected mapType passed in referencedStyles - ${content.mapType}`
|
|
139
|
+
)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
})
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (elementContent.style) {
|
|
146
|
+
elementContent.style = UIDLUtils.transformStylesAssignmentsToJson(
|
|
147
|
+
elementContent.style as Record<string, unknown>
|
|
148
|
+
)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (elementContent.attrs) {
|
|
152
|
+
elementContent.attrs = UIDLUtils.transformAttributesAssignmentsToJson(
|
|
153
|
+
elementContent.attrs as Record<string, unknown>
|
|
154
|
+
)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// @ts-ignore
|
|
158
|
+
if (elementContent.abilities?.link) {
|
|
159
|
+
// @ts-ignore
|
|
160
|
+
const { content, type } = elementContent.abilities?.link
|
|
161
|
+
if (type === 'url' && typeof content.url === 'string') {
|
|
162
|
+
content.url = UIDLUtils.transformStringAssignmentToJson(content.url)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (Array.isArray(elementContent.children)) {
|
|
167
|
+
elementContent.children = elementContent.children.map((child) => {
|
|
168
|
+
if (typeof child === 'string') {
|
|
169
|
+
return UIDLUtils.transformStringAssignmentToJson(child)
|
|
170
|
+
} else {
|
|
171
|
+
return parseComponentNode(child)
|
|
172
|
+
}
|
|
173
|
+
}, [])
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return node as unknown as UIDLNode
|
|
177
|
+
|
|
178
|
+
case 'conditional':
|
|
179
|
+
const conditionalNode = node as unknown as UIDLConditionalNode
|
|
180
|
+
const { reference } = conditionalNode.content
|
|
181
|
+
|
|
182
|
+
conditionalNode.content.node = parseComponentNode(
|
|
183
|
+
conditionalNode.content.node as unknown as Record<string, unknown>
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
if (typeof reference === 'string') {
|
|
187
|
+
conditionalNode.content.reference = UIDLUtils.transformStringAssignmentToJson(
|
|
188
|
+
reference
|
|
189
|
+
) as UIDLDynamicReference
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return conditionalNode
|
|
193
|
+
|
|
194
|
+
case 'repeat':
|
|
195
|
+
const repeatNode = node as unknown as UIDLRepeatNode
|
|
196
|
+
const { dataSource } = repeatNode.content
|
|
197
|
+
|
|
198
|
+
repeatNode.content.node = parseComponentNode(
|
|
199
|
+
repeatNode.content.node as unknown as Record<string, unknown>
|
|
200
|
+
) as UIDLElementNode
|
|
201
|
+
|
|
202
|
+
if (typeof dataSource === 'string') {
|
|
203
|
+
repeatNode.content.dataSource = UIDLUtils.transformStringAssignmentToJson(dataSource)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return repeatNode
|
|
207
|
+
|
|
208
|
+
case 'slot':
|
|
209
|
+
const slotNode = node as unknown as UIDLSlotNode
|
|
210
|
+
|
|
211
|
+
if (slotNode.content.fallback) {
|
|
212
|
+
slotNode.content.fallback = parseComponentNode(
|
|
213
|
+
slotNode.content.fallback as unknown as Record<string, unknown>
|
|
214
|
+
) as UIDLElementNode | UIDLStaticValue | UIDLDynamicReference
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return slotNode
|
|
218
|
+
|
|
219
|
+
case 'dynamic':
|
|
220
|
+
case 'static':
|
|
221
|
+
case 'raw':
|
|
222
|
+
return node as unknown as UIDLNode
|
|
223
|
+
|
|
224
|
+
default:
|
|
225
|
+
throw new ParserError(`parseComponentNode attempted to parsed invalid node type ${node.type}`)
|
|
226
|
+
}
|
|
227
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ProjectUIDL,
|
|
3
|
+
ComponentUIDL,
|
|
4
|
+
ProjectValidationError,
|
|
5
|
+
ComponentValidationError,
|
|
6
|
+
VComponentUIDL,
|
|
7
|
+
VProjectUIDL,
|
|
8
|
+
} from '@teleporthq/teleport-types'
|
|
9
|
+
import { componentUIDLDecoder, rootComponentUIDLDecoder, projectUIDLDecoder } from '../decoders'
|
|
10
|
+
import * as utils from './utils'
|
|
11
|
+
|
|
12
|
+
interface ValidationResult {
|
|
13
|
+
valid: boolean
|
|
14
|
+
errorMsg: string
|
|
15
|
+
componentUIDL?: VComponentUIDL
|
|
16
|
+
projectUIDL?: VProjectUIDL
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export default class Validator {
|
|
20
|
+
public validateComponentSchema(input: Record<string, unknown>): ValidationResult {
|
|
21
|
+
try {
|
|
22
|
+
const cleanedUIDL = utils.validateNulls(input)
|
|
23
|
+
const uidl = componentUIDLDecoder.runWithException(cleanedUIDL)
|
|
24
|
+
|
|
25
|
+
return { valid: true, errorMsg: '', componentUIDL: uidl }
|
|
26
|
+
} catch (e) {
|
|
27
|
+
const errorMsg = utils.formatErrors([{ kind: e.kind, message: e.message, at: e.at }])
|
|
28
|
+
throw new ComponentValidationError(`Component - ${input?.name} \n
|
|
29
|
+
${errorMsg}`)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
public validateRootComponentSchema(input: Record<string, unknown>): ValidationResult {
|
|
34
|
+
try {
|
|
35
|
+
const cleanedUIDL = utils.validateNulls(input)
|
|
36
|
+
const uidl = rootComponentUIDLDecoder.runWithException(cleanedUIDL)
|
|
37
|
+
|
|
38
|
+
return { valid: true, errorMsg: '', componentUIDL: uidl }
|
|
39
|
+
} catch (e) {
|
|
40
|
+
const errorMsg = utils.formatErrors([{ kind: e.kind, message: e.message, at: e.at }])
|
|
41
|
+
throw new ComponentValidationError(`Component - ${input?.name}\n
|
|
42
|
+
${errorMsg}`)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
public validateProjectSchema(input: Record<string, unknown>): ValidationResult {
|
|
47
|
+
try {
|
|
48
|
+
const cleanedUIDL = utils.validateNulls(input)
|
|
49
|
+
const uidl = projectUIDLDecoder.runWithException(cleanedUIDL)
|
|
50
|
+
|
|
51
|
+
return { valid: true, errorMsg: '', projectUIDL: uidl }
|
|
52
|
+
} catch (e) {
|
|
53
|
+
const errorMsg = utils.formatErrors([{ kind: e.kind, message: e.message, at: e.at }])
|
|
54
|
+
throw new ProjectValidationError(`${input.name}\n
|
|
55
|
+
${errorMsg}`)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
public validateComponentContent(input: ComponentUIDL): ValidationResult {
|
|
60
|
+
const errorsInDefinitions = utils.checkDynamicDefinitions(
|
|
61
|
+
input as unknown as Record<string, unknown>
|
|
62
|
+
)
|
|
63
|
+
const errorsWithLocalVariables = utils.checkForLocalVariables(input)
|
|
64
|
+
|
|
65
|
+
utils.checkForDuplicateDefinitions(input)
|
|
66
|
+
const errors = [...errorsInDefinitions, ...errorsWithLocalVariables]
|
|
67
|
+
|
|
68
|
+
if (errors.length > 0) {
|
|
69
|
+
throw new ComponentValidationError(
|
|
70
|
+
`UIDL Component Content Validation Error. Please check the following: \n${errors}`
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return { valid: true, errorMsg: '' }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
public validateProjectContent(input: ProjectUIDL): ValidationResult {
|
|
78
|
+
const errorsOnRouteNode = utils.checkRouteDefinition(input) || []
|
|
79
|
+
let allErrors = errorsOnRouteNode
|
|
80
|
+
|
|
81
|
+
if (errorsOnRouteNode.length === 0) {
|
|
82
|
+
const errorsInStyleSet = utils.checkProjectStyleSet(input)
|
|
83
|
+
const errorsInRootComponent = utils.checkRootComponent(input)
|
|
84
|
+
|
|
85
|
+
const errorsWithComponentNaming = utils.checkComponentNaming(input)
|
|
86
|
+
const errorsWtihComponentExistence = utils.checkComponentExistenceAndReferences(input)
|
|
87
|
+
|
|
88
|
+
allErrors = [
|
|
89
|
+
...errorsOnRouteNode,
|
|
90
|
+
...errorsWtihComponentExistence,
|
|
91
|
+
...errorsWithComponentNaming,
|
|
92
|
+
...errorsInRootComponent,
|
|
93
|
+
...errorsInStyleSet,
|
|
94
|
+
]
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (allErrors.length > 0) {
|
|
98
|
+
throw new ProjectValidationError(
|
|
99
|
+
`UIDL Project Content Validation Error. Please check the following: ${allErrors}`
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return { valid: true, errorMsg: '' }
|
|
104
|
+
}
|
|
105
|
+
}
|