@platformos/platformos-graph 0.0.2
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/CHANGELOG.md +10 -0
- package/README.md +83 -0
- package/bin/jsconfig.json +18 -0
- package/bin/platformos-graph +107 -0
- package/dist/getWebComponentMap.d.ts +10 -0
- package/dist/getWebComponentMap.js +66 -0
- package/dist/getWebComponentMap.js.map +1 -0
- package/dist/graph/augment.d.ts +2 -0
- package/dist/graph/augment.js +22 -0
- package/dist/graph/augment.js.map +1 -0
- package/dist/graph/build.d.ts +3 -0
- package/dist/graph/build.js +31 -0
- package/dist/graph/build.js.map +1 -0
- package/dist/graph/module.d.ts +10 -0
- package/dist/graph/module.js +181 -0
- package/dist/graph/module.js.map +1 -0
- package/dist/graph/serialize.d.ts +2 -0
- package/dist/graph/serialize.js +18 -0
- package/dist/graph/serialize.js.map +1 -0
- package/dist/graph/test-helpers.d.ts +33 -0
- package/dist/graph/test-helpers.js +49 -0
- package/dist/graph/test-helpers.js.map +1 -0
- package/dist/graph/traverse.d.ts +14 -0
- package/dist/graph/traverse.js +458 -0
- package/dist/graph/traverse.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +31 -0
- package/dist/index.js.map +1 -0
- package/dist/toSourceCode.d.ts +8 -0
- package/dist/toSourceCode.js +76 -0
- package/dist/toSourceCode.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/types.d.ts +144 -0
- package/dist/types.js +13 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/index.d.ts +11 -0
- package/dist/utils/index.js +47 -0
- package/dist/utils/index.js.map +1 -0
- package/docs/graph.png +0 -0
- package/docs/how-it-works.md +89 -0
- package/fixtures/skeleton/app/views/partials/child.liquid +9 -0
- package/fixtures/skeleton/app/views/partials/parent.liquid +9 -0
- package/fixtures/skeleton/assets/theme.css +0 -0
- package/fixtures/skeleton/assets/theme.js +7 -0
- package/fixtures/skeleton/blocks/_private.liquid +1 -0
- package/fixtures/skeleton/blocks/_static.liquid +10 -0
- package/fixtures/skeleton/blocks/group.liquid +27 -0
- package/fixtures/skeleton/blocks/render-static.liquid +22 -0
- package/fixtures/skeleton/blocks/text.liquid +14 -0
- package/fixtures/skeleton/jsconfig.json +9 -0
- package/fixtures/skeleton/layout/theme.liquid +14 -0
- package/fixtures/skeleton/sections/custom-section.liquid +6 -0
- package/fixtures/skeleton/sections/header-group.json +36 -0
- package/fixtures/skeleton/sections/header.liquid +1 -0
- package/fixtures/skeleton/templates/index.json +20 -0
- package/package.json +41 -0
- package/src/getWebComponentMap.ts +81 -0
- package/src/graph/augment.ts +34 -0
- package/src/graph/build.spec.ts +248 -0
- package/src/graph/build.ts +45 -0
- package/src/graph/module.ts +212 -0
- package/src/graph/serialize.spec.ts +62 -0
- package/src/graph/serialize.ts +20 -0
- package/src/graph/test-helpers.ts +57 -0
- package/src/graph/traverse.ts +639 -0
- package/src/index.ts +5 -0
- package/src/toSourceCode.ts +80 -0
- package/src/types.ts +213 -0
- package/src/utils/index.ts +51 -0
- package/tsconfig.build.json +20 -0
- package/tsconfig.json +37 -0
|
@@ -0,0 +1,639 @@
|
|
|
1
|
+
import { NamedTags, NodeTypes } from '@platformos/liquid-html-parser';
|
|
2
|
+
import {
|
|
3
|
+
JSONNode,
|
|
4
|
+
nodeAtPath,
|
|
5
|
+
ObjectNode,
|
|
6
|
+
parseJSON,
|
|
7
|
+
path,
|
|
8
|
+
Preset,
|
|
9
|
+
Section,
|
|
10
|
+
SectionSchema,
|
|
11
|
+
SourceCodeType,
|
|
12
|
+
Template,
|
|
13
|
+
ThemeBlock,
|
|
14
|
+
ThemeBlockSchema,
|
|
15
|
+
visit,
|
|
16
|
+
Visitor,
|
|
17
|
+
} from '@platformos/platformos-check-common';
|
|
18
|
+
import {
|
|
19
|
+
AugmentedDependencies,
|
|
20
|
+
JsonModule,
|
|
21
|
+
JsonModuleKind,
|
|
22
|
+
LiquidModule,
|
|
23
|
+
LiquidModuleKind,
|
|
24
|
+
ModuleType,
|
|
25
|
+
Range,
|
|
26
|
+
Reference,
|
|
27
|
+
ThemeGraph,
|
|
28
|
+
ThemeModule,
|
|
29
|
+
Void,
|
|
30
|
+
} from '../types';
|
|
31
|
+
import { acceptsLocalBlocks, assertNever, exists, isString, unexpected, unique } from '../utils';
|
|
32
|
+
import {
|
|
33
|
+
getAssetModule,
|
|
34
|
+
getLayoutModule,
|
|
35
|
+
getSectionGroupModule,
|
|
36
|
+
getSectionModule,
|
|
37
|
+
getPartialModule,
|
|
38
|
+
getThemeBlockModule,
|
|
39
|
+
} from './module';
|
|
40
|
+
|
|
41
|
+
export async function traverseModule(
|
|
42
|
+
module: ThemeModule,
|
|
43
|
+
themeGraph: ThemeGraph,
|
|
44
|
+
deps: AugmentedDependencies,
|
|
45
|
+
): Promise<Void> {
|
|
46
|
+
// If the module is already traversed, skip it
|
|
47
|
+
if (themeGraph.modules[module.uri]) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Signal to all users that the file is being traversed
|
|
52
|
+
// This will prevent multiple traversals of the same file
|
|
53
|
+
themeGraph.modules[module.uri] = module;
|
|
54
|
+
|
|
55
|
+
// Check if the module exists on disk
|
|
56
|
+
module.exists = await exists(deps.fs, module.uri);
|
|
57
|
+
|
|
58
|
+
// If the module doesn't exist, we can't traverse it
|
|
59
|
+
if (!module.exists) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
switch (module.type) {
|
|
64
|
+
case ModuleType.Liquid: {
|
|
65
|
+
return traverseLiquidModule(module, themeGraph, deps);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
case ModuleType.Json: {
|
|
69
|
+
return traverseJsonModule(module, themeGraph, deps);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
case ModuleType.JavaScript: {
|
|
73
|
+
return; // TODO graph import/exports ?
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
case ModuleType.Css:
|
|
77
|
+
case ModuleType.Svg:
|
|
78
|
+
case ModuleType.Image: {
|
|
79
|
+
return; // Nothing to do??
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
default: {
|
|
83
|
+
return assertNever(module);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function traverseLiquidModule(
|
|
89
|
+
module: LiquidModule,
|
|
90
|
+
themeGraph: ThemeGraph,
|
|
91
|
+
deps: AugmentedDependencies,
|
|
92
|
+
) {
|
|
93
|
+
const sourceCode = await deps.getSourceCode(module.uri);
|
|
94
|
+
|
|
95
|
+
if (sourceCode.ast instanceof Error) return; // can't visit what you can't parse
|
|
96
|
+
|
|
97
|
+
const visitor: Visitor<
|
|
98
|
+
SourceCodeType.LiquidHtml,
|
|
99
|
+
{ target: ThemeModule; sourceRange: Range; targetRange?: Range }
|
|
100
|
+
> = {
|
|
101
|
+
// {{ 'theme.js' | asset_url }}
|
|
102
|
+
// {{ 'image.png' | asset_img_url }}
|
|
103
|
+
// {{ 'icon.svg' | inline_asset_content }}
|
|
104
|
+
LiquidFilter: async (node, ancestors) => {
|
|
105
|
+
if (['asset_url', 'asset_img_url', 'inline_asset_content'].includes(node.name)) {
|
|
106
|
+
const parentNode = ancestors[ancestors.length - 1]!;
|
|
107
|
+
if (parentNode.type !== NodeTypes.LiquidVariable) return;
|
|
108
|
+
if (parentNode.expression.type !== NodeTypes.String) return;
|
|
109
|
+
if (parentNode.filters[0] !== node) return;
|
|
110
|
+
const asset = parentNode.expression.value;
|
|
111
|
+
const module = getAssetModule(themeGraph, asset);
|
|
112
|
+
if (!module) return;
|
|
113
|
+
return {
|
|
114
|
+
target: module,
|
|
115
|
+
sourceRange: [parentNode.position.start, parentNode.position.end],
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
// {% content_for 'block', type: 'staticBlockName', id: 'id' %}
|
|
121
|
+
ContentForMarkup: async (node, ancestors) => {
|
|
122
|
+
const parentNode = ancestors.at(-1)!;
|
|
123
|
+
if (node.contentForType.value !== 'block') return;
|
|
124
|
+
const blockTypeArg = node.args.find((arg) => arg.name === 'type');
|
|
125
|
+
if (!blockTypeArg) return;
|
|
126
|
+
|
|
127
|
+
const blockTypeValue = blockTypeArg.value;
|
|
128
|
+
if (blockTypeValue.type !== NodeTypes.String) return;
|
|
129
|
+
|
|
130
|
+
const blockType = blockTypeValue.value;
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
target: getThemeBlockModule(themeGraph, blockType),
|
|
134
|
+
sourceRange: [parentNode.position.start, node.position.end],
|
|
135
|
+
};
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
// <custom-element></custom-element>
|
|
139
|
+
HtmlElement: async (node) => {
|
|
140
|
+
if (node.name.length !== 1) return;
|
|
141
|
+
if (node.name[0].type !== NodeTypes.TextNode) return;
|
|
142
|
+
const nodeNameNode = node.name[0];
|
|
143
|
+
const nodeName = nodeNameNode.value;
|
|
144
|
+
if (!nodeName.includes('-')) return; // skip non-custom-elements
|
|
145
|
+
|
|
146
|
+
const result = deps.getWebComponentDefinitionReference(nodeName);
|
|
147
|
+
if (!result) return;
|
|
148
|
+
const { assetName, range } = result;
|
|
149
|
+
const module = getAssetModule(themeGraph, assetName);
|
|
150
|
+
if (!module) return;
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
target: module,
|
|
154
|
+
sourceRange: [node.blockStartPosition.start, nodeNameNode.position.end],
|
|
155
|
+
targetRange: range,
|
|
156
|
+
};
|
|
157
|
+
},
|
|
158
|
+
|
|
159
|
+
// {% render 'partial' %}
|
|
160
|
+
RenderMarkup: async (node, ancestors) => {
|
|
161
|
+
const snippet = node.snippet;
|
|
162
|
+
const tag = ancestors.at(-1)!;
|
|
163
|
+
if (!isString(snippet) && snippet.type === NodeTypes.String) {
|
|
164
|
+
return {
|
|
165
|
+
target: getPartialModule(themeGraph, snippet.value),
|
|
166
|
+
sourceRange: [tag.position.start, tag.position.end],
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
|
|
171
|
+
LiquidTag: async (node) => {
|
|
172
|
+
switch (node.name) {
|
|
173
|
+
// {% sections 'section-group' %}
|
|
174
|
+
case NamedTags.sections: {
|
|
175
|
+
if (!isString(node.markup)) {
|
|
176
|
+
const sectionGroupType = node.markup.value;
|
|
177
|
+
return {
|
|
178
|
+
target: getSectionGroupModule(themeGraph, sectionGroupType),
|
|
179
|
+
sourceRange: [node.position.start, node.position.end],
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// {% section 'section' %}
|
|
185
|
+
case NamedTags.section: {
|
|
186
|
+
if (!isString(node.markup)) {
|
|
187
|
+
const sectionType = node.markup.value;
|
|
188
|
+
return {
|
|
189
|
+
target: getSectionModule(themeGraph, sectionType),
|
|
190
|
+
sourceRange: [node.position.start, node.position.end],
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
const references = await visit(sourceCode.ast, visitor);
|
|
199
|
+
|
|
200
|
+
for (const reference of references) {
|
|
201
|
+
bind(module, reference.target, {
|
|
202
|
+
sourceRange: reference.sourceRange,
|
|
203
|
+
targetRange: reference.targetRange,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const modules = unique(references.map((ref) => ref.target));
|
|
208
|
+
const promises = modules.map((mod) => traverseModule(mod, themeGraph, deps));
|
|
209
|
+
|
|
210
|
+
// Look at schema references if any
|
|
211
|
+
if (module.kind === LiquidModuleKind.Section) {
|
|
212
|
+
const sectionName = path.basename(module.uri, '.liquid');
|
|
213
|
+
const sectionSchema = await deps.getSectionSchema(sectionName);
|
|
214
|
+
promises.push(traverseLiquidSchema(sectionSchema, module, themeGraph, deps));
|
|
215
|
+
} else if (module.kind === LiquidModuleKind.Block) {
|
|
216
|
+
const blockName = path.basename(module.uri, '.liquid');
|
|
217
|
+
const blockSchema = await deps.getBlockSchema(blockName);
|
|
218
|
+
promises.push(traverseLiquidSchema(blockSchema, module, themeGraph, deps));
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return Promise.all(promises);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async function traverseLiquidSchema(
|
|
225
|
+
schema: SectionSchema | ThemeBlockSchema | undefined,
|
|
226
|
+
module: LiquidModule,
|
|
227
|
+
themeGraph: ThemeGraph,
|
|
228
|
+
deps: AugmentedDependencies,
|
|
229
|
+
): Promise<Void> {
|
|
230
|
+
if (!schema) return;
|
|
231
|
+
|
|
232
|
+
const isSection = module.kind === LiquidModuleKind.Section;
|
|
233
|
+
const hasLocalBlocks =
|
|
234
|
+
isSection && (await acceptsLocalBlocks(path.basename(module.uri, '.liquid'), deps));
|
|
235
|
+
if (hasLocalBlocks) return;
|
|
236
|
+
|
|
237
|
+
const { ast, validSchema } = schema;
|
|
238
|
+
if (validSchema instanceof Error || ast instanceof Error) return;
|
|
239
|
+
|
|
240
|
+
const promises: Promise<Void>[] = [];
|
|
241
|
+
|
|
242
|
+
// Traverse the blocks
|
|
243
|
+
if (validSchema.blocks) {
|
|
244
|
+
promises.push(traverseSchemaBlocks(schema, module, ast, validSchema.blocks, themeGraph, deps));
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Traverse the presets
|
|
248
|
+
if (validSchema.presets) {
|
|
249
|
+
promises.push(
|
|
250
|
+
traverseSchemaPresets(schema, module, ast, validSchema.presets, themeGraph, deps),
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Traverse section.default if it exists
|
|
255
|
+
if ('default' in validSchema && validSchema.default) {
|
|
256
|
+
promises.push(
|
|
257
|
+
traverseSchemaDefault(
|
|
258
|
+
schema as SectionSchema,
|
|
259
|
+
module,
|
|
260
|
+
ast,
|
|
261
|
+
validSchema.default,
|
|
262
|
+
themeGraph,
|
|
263
|
+
deps,
|
|
264
|
+
),
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return Promise.all(promises);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
async function traverseSchemaBlocks(
|
|
272
|
+
schema: SectionSchema | ThemeBlockSchema,
|
|
273
|
+
module: LiquidModule,
|
|
274
|
+
ast: JSONNode,
|
|
275
|
+
blocks: Section.Block[] | ThemeBlock.Block[],
|
|
276
|
+
themeGraph: ThemeGraph,
|
|
277
|
+
deps: AugmentedDependencies,
|
|
278
|
+
) {
|
|
279
|
+
const promises: Promise<Void>[] = [];
|
|
280
|
+
|
|
281
|
+
for (const [i, blockDef] of Object.entries(blocks)) {
|
|
282
|
+
const nodePath = ['blocks', i];
|
|
283
|
+
const node = nodeAtPath(ast, nodePath)! as ObjectNode;
|
|
284
|
+
const typeProperty = node.children.find((child) => child.key.value === 'type');
|
|
285
|
+
if (!typeProperty) continue;
|
|
286
|
+
|
|
287
|
+
const sourceRange: Range = [
|
|
288
|
+
schema.offset + typeProperty.loc.start.offset,
|
|
289
|
+
schema.offset + typeProperty.loc.end.offset,
|
|
290
|
+
];
|
|
291
|
+
|
|
292
|
+
// blocks: [{ "type": "@theme" }, { "type": "custom-block" }]
|
|
293
|
+
switch (blockDef.type) {
|
|
294
|
+
case '@theme': {
|
|
295
|
+
const publicBlocks = await deps
|
|
296
|
+
.getThemeBlockNames()
|
|
297
|
+
.then((blocks) => blocks.filter((name) => !name.startsWith('_')));
|
|
298
|
+
for (const publicBlock of publicBlocks) {
|
|
299
|
+
const blockModule = getThemeBlockModule(
|
|
300
|
+
themeGraph,
|
|
301
|
+
path.basename(publicBlock, '.liquid'),
|
|
302
|
+
);
|
|
303
|
+
bind(module, blockModule, { sourceRange, type: 'indirect' });
|
|
304
|
+
promises.push(traverseModule(blockModule, themeGraph, deps));
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
break;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
case '@app': {
|
|
311
|
+
break;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
default: {
|
|
315
|
+
const blockModule = getThemeBlockModule(themeGraph, blockDef.type);
|
|
316
|
+
bind(module, blockModule, { sourceRange });
|
|
317
|
+
promises.push(traverseModule(blockModule, themeGraph, deps));
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return Promise.all(promises);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
async function traverseSchemaPresets(
|
|
326
|
+
schema: SectionSchema | ThemeBlockSchema,
|
|
327
|
+
module: LiquidModule,
|
|
328
|
+
ast: JSONNode,
|
|
329
|
+
presets: Preset.Preset[],
|
|
330
|
+
themeGraph: ThemeGraph,
|
|
331
|
+
deps: AugmentedDependencies,
|
|
332
|
+
) {
|
|
333
|
+
const promises: Promise<Void>[] = [];
|
|
334
|
+
|
|
335
|
+
for (const [i, preset] of presets.entries()) {
|
|
336
|
+
if (!('blocks' in preset)) continue;
|
|
337
|
+
|
|
338
|
+
// Iterate over array entries or object entries depending on how the blocks are defined
|
|
339
|
+
const iterator = Array.isArray(preset.blocks)
|
|
340
|
+
? preset.blocks.entries()
|
|
341
|
+
: Object.entries(preset.blocks!);
|
|
342
|
+
|
|
343
|
+
for (const [keyOrIndex, block] of iterator) {
|
|
344
|
+
const nodePath = ['presets', i, 'blocks', keyOrIndex];
|
|
345
|
+
const node = nodeAtPath(ast, nodePath)! as ObjectNode;
|
|
346
|
+
|
|
347
|
+
const blockModule = getThemeBlockModule(themeGraph, block.type);
|
|
348
|
+
if (!blockModule) continue;
|
|
349
|
+
|
|
350
|
+
const typeProperty = node.children.find((child) => child.key.value === 'type');
|
|
351
|
+
if (!typeProperty) continue;
|
|
352
|
+
|
|
353
|
+
const sourceRange: Range = [
|
|
354
|
+
schema.offset + typeProperty.loc.start.offset,
|
|
355
|
+
schema.offset + typeProperty.loc.end.offset,
|
|
356
|
+
];
|
|
357
|
+
|
|
358
|
+
bind(module, blockModule, { sourceRange, type: 'preset' });
|
|
359
|
+
promises.push(traverseModule(blockModule, themeGraph, deps));
|
|
360
|
+
if (block.blocks) {
|
|
361
|
+
promises.push(
|
|
362
|
+
traverseSchemaPresetBlock(schema, module, ast, block.blocks, nodePath, themeGraph, deps),
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
return Promise.all(promises);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
async function traverseSchemaPresetBlock(
|
|
372
|
+
schema: SectionSchema | ThemeBlockSchema,
|
|
373
|
+
module: LiquidModule,
|
|
374
|
+
ast: JSONNode,
|
|
375
|
+
blocks: Preset.PresetBlockHash | Preset.PresetBlockForArray[],
|
|
376
|
+
parentPath: (string | number)[],
|
|
377
|
+
themeGraph: ThemeGraph,
|
|
378
|
+
deps: AugmentedDependencies,
|
|
379
|
+
) {
|
|
380
|
+
const promises: Promise<Void>[] = [];
|
|
381
|
+
|
|
382
|
+
// Iterate over array entries or object entries depending on how the blocks are defined
|
|
383
|
+
const iterator = Array.isArray(blocks) ? blocks.entries() : Object.entries(blocks);
|
|
384
|
+
|
|
385
|
+
for (const [keyOrIndex, block] of iterator) {
|
|
386
|
+
const nodePath = [...parentPath, 'blocks', keyOrIndex];
|
|
387
|
+
const node = nodeAtPath(ast, nodePath)! as ObjectNode;
|
|
388
|
+
|
|
389
|
+
const blockModule = getThemeBlockModule(themeGraph, block.type);
|
|
390
|
+
if (!blockModule) continue;
|
|
391
|
+
|
|
392
|
+
const typeProperty = node.children.find((child) => child.key.value === 'type');
|
|
393
|
+
if (!typeProperty) continue;
|
|
394
|
+
|
|
395
|
+
const sourceRange: Range = [
|
|
396
|
+
schema.offset + typeProperty.loc.start.offset,
|
|
397
|
+
schema.offset + typeProperty.loc.end.offset,
|
|
398
|
+
];
|
|
399
|
+
|
|
400
|
+
bind(module, blockModule, { sourceRange, type: 'preset' });
|
|
401
|
+
promises.push(traverseModule(blockModule, themeGraph, deps));
|
|
402
|
+
if (block.blocks) {
|
|
403
|
+
promises.push(
|
|
404
|
+
traverseSchemaPresetBlock(schema, module, ast, block.blocks, nodePath, themeGraph, deps),
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
return Promise.all(promises);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
async function traverseSchemaDefault(
|
|
413
|
+
schema: SectionSchema,
|
|
414
|
+
module: LiquidModule,
|
|
415
|
+
ast: JSONNode,
|
|
416
|
+
preset: Section.Default,
|
|
417
|
+
themeGraph: ThemeGraph,
|
|
418
|
+
deps: AugmentedDependencies,
|
|
419
|
+
) {
|
|
420
|
+
const promises: Promise<Void>[] = [];
|
|
421
|
+
|
|
422
|
+
if (!('blocks' in preset)) return;
|
|
423
|
+
|
|
424
|
+
// Iterate over array entries or object entries depending on how the blocks are defined
|
|
425
|
+
const iterator = Array.isArray(preset.blocks)
|
|
426
|
+
? preset.blocks.entries()
|
|
427
|
+
: Object.entries(preset.blocks!);
|
|
428
|
+
|
|
429
|
+
for (const [keyOrIndex, block] of iterator) {
|
|
430
|
+
const nodePath = ['default', 'blocks', keyOrIndex];
|
|
431
|
+
const node = nodeAtPath(ast, nodePath)! as ObjectNode;
|
|
432
|
+
|
|
433
|
+
const blockModule = getThemeBlockModule(themeGraph, block.type);
|
|
434
|
+
if (!blockModule) continue;
|
|
435
|
+
|
|
436
|
+
const typeProperty = node.children.find((child) => child.key.value === 'type');
|
|
437
|
+
if (!typeProperty) continue;
|
|
438
|
+
|
|
439
|
+
const sourceRange: Range = [
|
|
440
|
+
schema.offset + typeProperty.loc.start.offset,
|
|
441
|
+
schema.offset + typeProperty.loc.end.offset,
|
|
442
|
+
];
|
|
443
|
+
|
|
444
|
+
bind(module, blockModule, { sourceRange, type: 'preset' });
|
|
445
|
+
promises.push(traverseModule(blockModule, themeGraph, deps));
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
return Promise.all(promises);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
async function traverseJsonModule(
|
|
452
|
+
module: JsonModule,
|
|
453
|
+
themeGraph: ThemeGraph,
|
|
454
|
+
deps: AugmentedDependencies,
|
|
455
|
+
): Promise<Void> {
|
|
456
|
+
const sourceCode = await deps.getSourceCode(module.uri);
|
|
457
|
+
if (sourceCode.type !== SourceCodeType.JSON) throw unexpected();
|
|
458
|
+
const ast = sourceCode.ast;
|
|
459
|
+
if (ast instanceof Error) return; // can't visit what you can't parse
|
|
460
|
+
|
|
461
|
+
switch (module.kind) {
|
|
462
|
+
case JsonModuleKind.Template: {
|
|
463
|
+
// Should only happen once per template
|
|
464
|
+
const template = parseJSON(sourceCode.source) as Template.Template;
|
|
465
|
+
const promises: Promise<Void>[] = [];
|
|
466
|
+
for (const [key, section] of Object.entries(template.sections)) {
|
|
467
|
+
const sectionType = section.type;
|
|
468
|
+
const path = ['sections', key];
|
|
469
|
+
const node = nodeAtPath(ast, path)! as ObjectNode;
|
|
470
|
+
const sectionModule = getSectionModule(themeGraph, sectionType);
|
|
471
|
+
const typeProperty = node.children.find((child) => child.key.value === 'type')!;
|
|
472
|
+
const start = typeProperty.loc.start.offset;
|
|
473
|
+
const end = typeProperty.loc.end.offset;
|
|
474
|
+
const sourceRange: Range = [start, end];
|
|
475
|
+
// Link the template to the section
|
|
476
|
+
bind(module, sectionModule, { sourceRange });
|
|
477
|
+
promises.push(
|
|
478
|
+
// Traverse the section themeselves
|
|
479
|
+
traverseModule(sectionModule, themeGraph, deps),
|
|
480
|
+
// Link the blocks used in the section to the template
|
|
481
|
+
traverseSectionReferences(module, ast, path, section, themeGraph, deps),
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Link the template to the layout
|
|
486
|
+
const layout = template.layout;
|
|
487
|
+
const layoutModule = getLayoutModule(themeGraph, template.layout);
|
|
488
|
+
if (layoutModule) {
|
|
489
|
+
let sourceRange: Range | undefined = undefined;
|
|
490
|
+
let indirect = true;
|
|
491
|
+
if (layout !== false && layout !== undefined) {
|
|
492
|
+
const layoutPath = ['layout'];
|
|
493
|
+
const node = nodeAtPath(ast, layoutPath)!;
|
|
494
|
+
sourceRange = [node.loc.start.offset, node.loc.end.offset];
|
|
495
|
+
indirect = false; // this is a direct reference to the layout
|
|
496
|
+
}
|
|
497
|
+
bind(module, layoutModule, { sourceRange, type: 'indirect' });
|
|
498
|
+
promises.push(traverseModule(layoutModule, themeGraph, deps));
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
return Promise.all(promises);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
case JsonModuleKind.SectionGroup: {
|
|
505
|
+
const sectionGroup = parseJSON(sourceCode.source) as Template.SectionGroup;
|
|
506
|
+
const promises: Promise<Void>[] = [];
|
|
507
|
+
for (const [key, section] of Object.entries(sectionGroup.sections)) {
|
|
508
|
+
const sectionType = section.type;
|
|
509
|
+
const path = ['sections', key];
|
|
510
|
+
const node = nodeAtPath(ast, path)! as ObjectNode;
|
|
511
|
+
const sectionModule = getSectionModule(themeGraph, sectionType);
|
|
512
|
+
|
|
513
|
+
const typeProperty = node.children.find((child) => child.key.value === 'type')!;
|
|
514
|
+
const start = typeProperty.loc.start.offset;
|
|
515
|
+
const end = typeProperty.loc.end.offset;
|
|
516
|
+
const sourceRange: Range = [start, end];
|
|
517
|
+
|
|
518
|
+
// Link the template to the section
|
|
519
|
+
bind(module, sectionModule, { sourceRange });
|
|
520
|
+
promises.push(
|
|
521
|
+
// Traverse the section themeselves
|
|
522
|
+
traverseModule(sectionModule, themeGraph, deps),
|
|
523
|
+
// Link the blocks used in the section to the template
|
|
524
|
+
traverseSectionReferences(module, ast, path, section, themeGraph, deps),
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
return Promise.all(promises);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
default: {
|
|
532
|
+
return assertNever(module.kind);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Traverses the actual references contained inside Template.Template['sections'] and Template.SectionGroup['sections'].
|
|
539
|
+
*
|
|
540
|
+
* Does nothing if the mode is not `GraphMode.Production`.
|
|
541
|
+
*/
|
|
542
|
+
async function traverseSectionReferences(
|
|
543
|
+
source: ThemeModule, // template or section group
|
|
544
|
+
sourceAst: JSONNode,
|
|
545
|
+
nodePath: string[] = [],
|
|
546
|
+
section: Template.Section,
|
|
547
|
+
themeGraph: ThemeGraph,
|
|
548
|
+
deps: AugmentedDependencies,
|
|
549
|
+
): Promise<Void> {
|
|
550
|
+
if (!section.blocks) return;
|
|
551
|
+
const sectionHasLocalBlocks = await acceptsLocalBlocks(section.type, deps);
|
|
552
|
+
if (sectionHasLocalBlocks) return;
|
|
553
|
+
|
|
554
|
+
const promises: Promise<Void>[] = [];
|
|
555
|
+
|
|
556
|
+
for (const [key, block] of Object.entries(section.blocks)) {
|
|
557
|
+
const blockType = block.type;
|
|
558
|
+
const blockModule = getThemeBlockModule(themeGraph, blockType);
|
|
559
|
+
const path = [...nodePath, 'blocks', key];
|
|
560
|
+
const node = nodeAtPath(sourceAst, path)! as ObjectNode;
|
|
561
|
+
const typeProperty = node.children.find((child) => child.key.value === 'type')!;
|
|
562
|
+
const start = typeProperty.loc.start.offset;
|
|
563
|
+
const end = typeProperty.loc.end.offset;
|
|
564
|
+
const sourceRange: Range = [start, end];
|
|
565
|
+
// Link the template to the block
|
|
566
|
+
bind(source, blockModule, { sourceRange });
|
|
567
|
+
promises.push(
|
|
568
|
+
// Traverse the block themselves
|
|
569
|
+
traverseModule(blockModule, themeGraph, deps),
|
|
570
|
+
// Traverse the block references
|
|
571
|
+
traverseBlockReferences(source, sourceAst, path, block, themeGraph, deps),
|
|
572
|
+
);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
return Promise.all(promises);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
async function traverseBlockReferences(
|
|
579
|
+
source: ThemeModule, // template or section group
|
|
580
|
+
sourceAst: JSONNode,
|
|
581
|
+
nodePath: string[] = [],
|
|
582
|
+
block: Template.Block,
|
|
583
|
+
themeGraph: ThemeGraph,
|
|
584
|
+
deps: AugmentedDependencies,
|
|
585
|
+
): Promise<Void> {
|
|
586
|
+
if (!block.blocks) return;
|
|
587
|
+
|
|
588
|
+
const promises: Promise<Void>[] = [];
|
|
589
|
+
for (const [key, childBlock] of Object.entries(block.blocks)) {
|
|
590
|
+
const childBlockType = childBlock.type;
|
|
591
|
+
const childBlockModule = getThemeBlockModule(themeGraph, childBlockType);
|
|
592
|
+
const path = [...nodePath, 'blocks', key];
|
|
593
|
+
const node = nodeAtPath(sourceAst, path)! as ObjectNode;
|
|
594
|
+
const typeProperty = node.children.find((child) => child.key.value === 'type')!;
|
|
595
|
+
const start = typeProperty.loc.start.offset;
|
|
596
|
+
const end = typeProperty.loc.end.offset;
|
|
597
|
+
const sourceRange: Range = [start, end];
|
|
598
|
+
// Link the template/section group to the block
|
|
599
|
+
bind(source, childBlockModule, { sourceRange });
|
|
600
|
+
promises.push(
|
|
601
|
+
// Traverse the child block themselves
|
|
602
|
+
traverseModule(childBlockModule, themeGraph, deps),
|
|
603
|
+
// Traverse the child block references
|
|
604
|
+
traverseBlockReferences(source, sourceAst, path, childBlock, themeGraph, deps),
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
return Promise.all(promises);
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
/**
|
|
612
|
+
* The bind method is the method that links two modules together.
|
|
613
|
+
*
|
|
614
|
+
* It adds the dependency to the source module's dependencies and the target module's references.
|
|
615
|
+
*
|
|
616
|
+
* This function mutates the source and target modules.
|
|
617
|
+
*/
|
|
618
|
+
export function bind(
|
|
619
|
+
source: ThemeModule,
|
|
620
|
+
target: ThemeModule,
|
|
621
|
+
{
|
|
622
|
+
sourceRange,
|
|
623
|
+
targetRange,
|
|
624
|
+
type = 'direct', // the type of dependency, can be 'direct', 'indirect' or 'preset'
|
|
625
|
+
}: {
|
|
626
|
+
sourceRange?: Range; // a range in the source module that references the child
|
|
627
|
+
targetRange?: Range; // a range in the child module that is being referenced
|
|
628
|
+
type?: Reference['type']; // the type of dependency
|
|
629
|
+
} = {},
|
|
630
|
+
): void {
|
|
631
|
+
const dependency: Reference = {
|
|
632
|
+
source: { uri: source.uri, range: sourceRange },
|
|
633
|
+
target: { uri: target.uri, range: targetRange },
|
|
634
|
+
type: type,
|
|
635
|
+
};
|
|
636
|
+
|
|
637
|
+
source.dependencies.push(dependency);
|
|
638
|
+
target.references.push(dependency);
|
|
639
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { buildThemeGraph } from './graph/build';
|
|
2
|
+
export { serializeThemeGraph } from './graph/serialize';
|
|
3
|
+
export { getWebComponentMap, findWebComponentReferences } from './getWebComponentMap';
|
|
4
|
+
export { toCssSourceCode, toJsSourceCode, toSourceCode, toSvgSourceCode } from './toSourceCode';
|
|
5
|
+
export * from './types';
|