@platformos/platformos-graph 0.0.20 → 0.2.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/CHANGELOG.md +622 -8
- package/dist/cache-probes.spec.d.ts +1 -0
- package/dist/cache-probes.spec.js +62 -0
- package/dist/cache-probes.spec.js.map +1 -0
- package/dist/cli.js +18 -13
- package/dist/cli.js.map +1 -1
- package/dist/cli.spec.d.ts +1 -0
- package/dist/cli.spec.js +235 -0
- package/dist/cli.spec.js.map +1 -0
- package/dist/graph/app-index-resolution.spec.d.ts +1 -0
- package/dist/graph/app-index-resolution.spec.js +167 -0
- package/dist/graph/app-index-resolution.spec.js.map +1 -0
- package/dist/graph/asset-resolution.spec.d.ts +1 -0
- package/dist/graph/asset-resolution.spec.js +64 -0
- package/dist/graph/asset-resolution.spec.js.map +1 -0
- package/dist/graph/augment.js +3 -0
- package/dist/graph/augment.js.map +1 -1
- package/dist/graph/build.d.ts +15 -2
- package/dist/graph/build.js +54 -12
- package/dist/graph/build.js.map +1 -1
- package/dist/graph/build.spec.d.ts +1 -0
- package/dist/graph/build.spec.js +138 -0
- package/dist/graph/build.spec.js.map +1 -0
- package/dist/graph/edge-sources.d.ts +28 -0
- package/dist/graph/edge-sources.js +43 -0
- package/dist/graph/edge-sources.js.map +1 -0
- package/dist/graph/edge-sources.spec.d.ts +1 -0
- package/dist/graph/edge-sources.spec.js +125 -0
- package/dist/graph/edge-sources.spec.js.map +1 -0
- package/dist/graph/extract.spec.d.ts +1 -0
- package/dist/graph/extract.spec.js +163 -0
- package/dist/graph/extract.spec.js.map +1 -0
- package/dist/graph/module.d.ts +60 -7
- package/dist/graph/module.js +137 -41
- package/dist/graph/module.js.map +1 -1
- package/dist/graph/module.spec.d.ts +1 -0
- package/dist/graph/module.spec.js +74 -0
- package/dist/graph/module.spec.js.map +1 -0
- package/dist/graph/query.d.ts +72 -0
- package/dist/graph/query.js +140 -0
- package/dist/graph/query.js.map +1 -0
- package/dist/graph/query.spec.d.ts +1 -0
- package/dist/graph/query.spec.js +209 -0
- package/dist/graph/query.spec.js.map +1 -0
- package/dist/graph/serialize.spec.d.ts +1 -0
- package/dist/graph/serialize.spec.js +50 -0
- package/dist/graph/serialize.spec.js.map +1 -0
- package/dist/graph/structural.spec.d.ts +1 -0
- package/dist/graph/structural.spec.js +146 -0
- package/dist/graph/structural.spec.js.map +1 -0
- package/dist/graph/test-helpers.js +3 -3
- package/dist/graph/test-helpers.js.map +1 -1
- package/dist/graph/traverse-edges.spec.d.ts +1 -0
- package/dist/graph/traverse-edges.spec.js +337 -0
- package/dist/graph/traverse-edges.spec.js.map +1 -0
- package/dist/graph/traverse.d.ts +51 -20
- package/dist/graph/traverse.js +239 -29
- package/dist/graph/traverse.js.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +19 -1
- package/dist/index.js.map +1 -1
- package/dist/parsers.d.ts +35 -0
- package/dist/parsers.js +72 -0
- package/dist/parsers.js.map +1 -0
- package/dist/parsers.spec.d.ts +1 -0
- package/dist/parsers.spec.js +70 -0
- package/dist/parsers.spec.js.map +1 -0
- package/dist/toSourceCode.d.ts +14 -2
- package/dist/toSourceCode.js +23 -19
- package/dist/toSourceCode.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types.d.ts +107 -3
- package/dist/types.js.map +1 -1
- package/dist/utils/index.d.ts +1 -1
- package/fixtures/asset-edges/app/assets/logo.css +1 -0
- package/fixtures/asset-edges/app/assets/site/app.js +1 -0
- package/fixtures/asset-edges/app/views/pages/index.liquid +3 -0
- package/fixtures/graphql-table/app/graphql/remote_table.graphql +9 -0
- package/fixtures/graphql-table/app/graphql/table_as_argument.graphql +5 -0
- package/fixtures/graphql-table/app/graphql/with_table.graphql +5 -0
- package/fixtures/graphql-table/app/graphql/without_table.graphql +3 -0
- package/fixtures/graphql-table/app/views/pages/index.liquid +6 -0
- package/fixtures/layout-edges/app/views/layouts/theme.liquid +4 -0
- package/fixtures/layout-edges/app/views/pages/broken.liquid +5 -0
- package/fixtures/layout-edges/app/views/pages/index.liquid +5 -0
- package/fixtures/schema-nodes/app/schema/blog_post.yml +6 -0
- package/fixtures/schema-nodes/app/schema/no_name.yml +3 -0
- package/fixtures/schema-nodes/app/views/pages/index.liquid +1 -0
- package/fixtures/structural/app/views/pages/about.liquid +1 -0
- package/fixtures/structural/app/views/pages/blog/show.liquid +4 -0
- package/fixtures/structural/app/views/pages/index.liquid +6 -0
- package/fixtures/structural/app/views/pages/rich.liquid +8 -0
- package/fixtures/structural/app/views/partials/card.liquid +1 -0
- package/fixtures/structural/app/views/partials/documented.liquid +5 -0
- package/package.json +7 -5
- package/src/cli.spec.ts +37 -31
- package/src/cli.ts +31 -15
- package/src/graph/augment.ts +4 -0
- package/src/graph/build.spec.ts +891 -6
- package/src/graph/build.ts +68 -17
- package/src/graph/edge-sources.spec.ts +125 -0
- package/src/graph/edge-sources.ts +49 -0
- package/src/graph/module.spec.ts +168 -0
- package/src/graph/module.ts +139 -45
- package/src/graph/query.spec.ts +260 -0
- package/src/graph/query.ts +154 -0
- package/src/graph/serialize.spec.ts +4 -4
- package/src/graph/test-helpers.ts +3 -3
- package/src/graph/{extract.spec.ts → traverse.spec.ts} +125 -2
- package/src/graph/traverse.ts +278 -37
- package/src/index.ts +16 -1
- package/src/parsers.spec.ts +85 -0
- package/src/parsers.ts +75 -0
- package/src/toSourceCode.ts +26 -26
- package/src/types.ts +111 -7
- package/src/utils/index.ts +1 -1
- package/dist/getWebComponentMap.d.ts +0 -10
- package/dist/getWebComponentMap.js +0 -66
- package/dist/getWebComponentMap.js.map +0 -1
- package/src/graph/traverse-edges.spec.ts +0 -278
- /package/fixtures/skeleton/{assets → app/assets}/app.css +0 -0
- /package/fixtures/skeleton/{assets → app/assets}/app.js +0 -0
package/src/index.ts
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
export { buildAppGraph } from './graph/build';
|
|
2
|
-
export {
|
|
2
|
+
export { enumerateEdgeSources, isEdgeSource } from './graph/edge-sources';
|
|
3
|
+
export { extractFileReferences, extractStructural } from './graph/traverse';
|
|
4
|
+
export {
|
|
5
|
+
dependenciesOf,
|
|
6
|
+
dependentsOf,
|
|
7
|
+
exists,
|
|
8
|
+
isEntryPoint,
|
|
9
|
+
isOrphan,
|
|
10
|
+
orphans,
|
|
11
|
+
reachableFrom,
|
|
12
|
+
missingDependencies,
|
|
13
|
+
missingTargets,
|
|
14
|
+
nearestModules,
|
|
15
|
+
} from './graph/query';
|
|
16
|
+
export type { NearestModulesOptions } from './graph/query';
|
|
3
17
|
export { serializeAppGraph } from './graph/serialize';
|
|
4
18
|
export { parseJs, toSourceCode } from './toSourceCode';
|
|
19
|
+
export { appBackedGetSourceCode, graphParsers } from './parsers';
|
|
5
20
|
export * from './types';
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { App, Parsers, SourceCodeType } from '@platformos/platformos-common';
|
|
2
|
+
import { toLiquidHTMLAST } from '@platformos/platformos-check-common';
|
|
3
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
4
|
+
import { appBackedGetSourceCode, graphParsers } from './parsers';
|
|
5
|
+
import { toSourceCode } from './toSourceCode';
|
|
6
|
+
|
|
7
|
+
const ROOT = 'file:///project';
|
|
8
|
+
const uri = (relativePath: string) => `${ROOT}/${relativePath}`;
|
|
9
|
+
|
|
10
|
+
const fsOver = (files: Record<string, string>) => ({
|
|
11
|
+
readFile: async (target: string) => {
|
|
12
|
+
const source = files[target];
|
|
13
|
+
if (source === undefined) throw new Error(`ENOENT: ${target}`);
|
|
14
|
+
return source;
|
|
15
|
+
},
|
|
16
|
+
stat: async () => {
|
|
17
|
+
throw new Error('not used');
|
|
18
|
+
},
|
|
19
|
+
readDirectory: async () => [],
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe('graphParsers', () => {
|
|
23
|
+
it('registers a JS parser and treats images as opaque', async () => {
|
|
24
|
+
const jsUri = uri('app/assets/app.js');
|
|
25
|
+
const pngUri = uri('app/assets/logo.png');
|
|
26
|
+
const files = { [jsUri]: 'export const a = 1;', [pngUri]: 'binary' };
|
|
27
|
+
const app = App.fromPaths(ROOT, [jsUri, pngUri], fsOver(files), graphParsers);
|
|
28
|
+
|
|
29
|
+
await app.load();
|
|
30
|
+
|
|
31
|
+
expect((app.get(jsUri)!.ast as { type: string }).type).toBe('Program');
|
|
32
|
+
expect(app.get(pngUri)!.ast).toEqual(new Error('File parsing not implemented'));
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('is the same JS parse the buffer path uses', async () => {
|
|
36
|
+
const source = 'export const a = 1;';
|
|
37
|
+
const jsUri = uri('app/assets/app.js');
|
|
38
|
+
const app = App.fromPaths(ROOT, [jsUri], fsOver({ [jsUri]: source }), graphParsers);
|
|
39
|
+
await app.load();
|
|
40
|
+
|
|
41
|
+
const fromBuffer = await toSourceCode(jsUri, source);
|
|
42
|
+
|
|
43
|
+
expect(app.get(jsUri)!.ast).toEqual(fromBuffer.ast);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe('appBackedGetSourceCode', () => {
|
|
48
|
+
const parsers = (
|
|
49
|
+
liquid = vi.fn(toLiquidHTMLAST),
|
|
50
|
+
): { parsers: Parsers; liquid: typeof liquid } => ({
|
|
51
|
+
parsers: { [SourceCodeType.LiquidHtml]: liquid, extensions: graphParsers.extensions },
|
|
52
|
+
liquid,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('parses a file at most once across a graph read and a lint read', async () => {
|
|
56
|
+
const cardUri = uri('app/views/partials/card.liquid');
|
|
57
|
+
const { parsers: registered, liquid } = parsers();
|
|
58
|
+
const app = App.fromPaths(ROOT, [cardUri], fsOver({ [cardUri]: '<b>card</b>' }), registered);
|
|
59
|
+
const getSourceCode = appBackedGetSourceCode(app, async () => {
|
|
60
|
+
throw new Error('fallback should not be needed');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// The graph asks for it…
|
|
64
|
+
const forGraph = await getSourceCode(cardUri);
|
|
65
|
+
void forGraph.ast;
|
|
66
|
+
// …and the lint reads the same file object.
|
|
67
|
+
await app.get(cardUri)!.load();
|
|
68
|
+
void app.get(cardUri)!.ast;
|
|
69
|
+
|
|
70
|
+
expect(forGraph).toBe(app.get(cardUri) as unknown as typeof forGraph);
|
|
71
|
+
expect(liquid.mock.calls.length).toBe(1);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('falls back for a URI the app does not contain', async () => {
|
|
75
|
+
const app = App.fromPaths(ROOT, [], fsOver({}), graphParsers);
|
|
76
|
+
const outside = 'file:///elsewhere/thing.liquid';
|
|
77
|
+
|
|
78
|
+
const result = await appBackedGetSourceCode(app, async (target) =>
|
|
79
|
+
toSourceCode(target, '<b>outside</b>'),
|
|
80
|
+
)(outside);
|
|
81
|
+
|
|
82
|
+
expect(result.uri).toBe(outside);
|
|
83
|
+
expect(result.source).toBe('<b>outside</b>');
|
|
84
|
+
});
|
|
85
|
+
});
|
package/src/parsers.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { App, AppFile, Parser, Parsers, UriString } from '@platformos/platformos-common';
|
|
2
|
+
import { asError } from '@platformos/platformos-check-common';
|
|
3
|
+
import { parse as acornParse, Program } from 'acorn';
|
|
4
|
+
|
|
5
|
+
import { FileSourceCode, SUPPORTED_ASSET_IMAGE_EXTENSIONS } from './types';
|
|
6
|
+
|
|
7
|
+
/** Parse a JavaScript asset, capturing a syntax error as a value rather than throwing. */
|
|
8
|
+
export function parseJs(source: string): Program | Error {
|
|
9
|
+
try {
|
|
10
|
+
return acornParse(source, {
|
|
11
|
+
ecmaVersion: 'latest',
|
|
12
|
+
sourceType: 'module',
|
|
13
|
+
});
|
|
14
|
+
} catch (error) {
|
|
15
|
+
return asError(error);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* A binary asset has nothing to parse. It is still a graph node — something links
|
|
21
|
+
* to it — so it gets an `Error` value rather than being absent, exactly as an
|
|
22
|
+
* unparseable file would.
|
|
23
|
+
*/
|
|
24
|
+
const parseOpaqueAsset: Parser = () => new Error('File parsing not implemented');
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The file types the GRAPH holds and the linter does not: `.js` assets and images.
|
|
28
|
+
*
|
|
29
|
+
* They exist for {@link toSourceCode}'s contract — every URI it is handed comes back as a
|
|
30
|
+
* `FileSourceCode` — and so an `App` an asset URI is put into can answer `ast` at all. NOT
|
|
31
|
+
* because a graph build reads them: `traverseModule` returns immediately for an Asset node, and
|
|
32
|
+
* the only question the graph asks about an asset is whether it EXISTS.
|
|
33
|
+
*
|
|
34
|
+
* Registering them on an {@link App} — rather than building a second set of file objects — is
|
|
35
|
+
* what lets a graph build and a lint run over the same project hold the SAME `AppFile`
|
|
36
|
+
* instances, so each file is read and parsed at most once for both. The language server does
|
|
37
|
+
* that; check-node builds no graph, so merging into `nodeParsers` would do nothing.
|
|
38
|
+
*
|
|
39
|
+
* THE MCP SUPERVISOR DELIBERATELY DOES NOT SHARE, and this is not a pending to-do: it builds no
|
|
40
|
+
* graph, parsing per request only the handful of files that could reference the edited one.
|
|
41
|
+
* Routing those through check-node's shared `App` would win nothing — `lintBuffers` parses the
|
|
42
|
+
* content it was HANDED and drops the app's entry on the way out — and would risk something,
|
|
43
|
+
* because that `App` carries UNSAVED buffers while its impact stage runs CONCURRENTLY with
|
|
44
|
+
* the lint.
|
|
45
|
+
*/
|
|
46
|
+
export const graphParsers: Parsers = {
|
|
47
|
+
extensions: {
|
|
48
|
+
js: parseJs,
|
|
49
|
+
...Object.fromEntries(
|
|
50
|
+
SUPPORTED_ASSET_IMAGE_EXTENSIONS.map((extension) => [extension, parseOpaqueAsset]),
|
|
51
|
+
),
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* A `getSourceCode` for the graph backed by an {@link App}.
|
|
57
|
+
*
|
|
58
|
+
* Pass it as `IDependencies.getSourceCode` and the graph stops parsing anything the
|
|
59
|
+
* lint has already parsed, and vice versa — they read the same file objects. Files
|
|
60
|
+
* the app does not contain fall through to `fallback`, which is how a URI outside
|
|
61
|
+
* the project (or an unclassified file) still gets a source code.
|
|
62
|
+
*/
|
|
63
|
+
export function appBackedGetSourceCode(
|
|
64
|
+
app: App,
|
|
65
|
+
fallback: (uri: UriString) => Promise<FileSourceCode>,
|
|
66
|
+
): (uri: UriString) => Promise<FileSourceCode> {
|
|
67
|
+
return async (uri: UriString): Promise<FileSourceCode> => {
|
|
68
|
+
const file: AppFile | undefined = app.get(uri);
|
|
69
|
+
if (!file) return fallback(uri);
|
|
70
|
+
await file.load();
|
|
71
|
+
// An AppFile is structurally a SourceCode; its `ast` is typed `unknown` only
|
|
72
|
+
// because platformos-common sits below the parsers that produce ASTs.
|
|
73
|
+
return file as unknown as FileSourceCode;
|
|
74
|
+
};
|
|
75
|
+
}
|
package/src/toSourceCode.ts
CHANGED
|
@@ -1,38 +1,38 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
} from '@platformos/platformos-check-common';
|
|
6
|
-
import { parse as acornParse, Program } from 'acorn';
|
|
7
|
-
import { AssetSourceCode, FileSourceCode, SUPPORTED_ASSET_IMAGE_EXTENSIONS } from './types';
|
|
1
|
+
import { toSourceCode as tcToSourceCode, UriString } from '@platformos/platformos-check-common';
|
|
2
|
+
import { sourceCodeTypeOf } from '@platformos/platformos-common';
|
|
3
|
+
import { AssetSourceCode, FileSourceCode } from './types';
|
|
4
|
+
import { graphParsers, parseJs } from './parsers';
|
|
8
5
|
import { extname } from './utils';
|
|
9
6
|
|
|
10
|
-
export
|
|
11
|
-
try {
|
|
12
|
-
return acornParse(source, {
|
|
13
|
-
ecmaVersion: 'latest',
|
|
14
|
-
sourceType: 'module',
|
|
15
|
-
});
|
|
16
|
-
} catch (error) {
|
|
17
|
-
return asError(error);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
7
|
+
export { parseJs };
|
|
20
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Build a source code for a graph node from contents already in hand.
|
|
11
|
+
*
|
|
12
|
+
* This is the path for callers with no `App`: an in-flight editor buffer, a fixture,
|
|
13
|
+
* a URI from outside the project. Callers that DO have one should pass
|
|
14
|
+
* `appBackedGetSourceCode(app, …)` as `getSourceCode` instead — that is what makes a
|
|
15
|
+
* graph build and a lint run share one parse per file rather than doing one each.
|
|
16
|
+
*
|
|
17
|
+
* The JS and image-asset parses come from {@link graphParsers}, so there is exactly
|
|
18
|
+
* one definition of how each is handled whether the file arrives through the App
|
|
19
|
+
* model or through here.
|
|
20
|
+
*/
|
|
21
21
|
export async function toSourceCode(uri: UriString, source: string): Promise<FileSourceCode> {
|
|
22
22
|
const extension = extname(uri);
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
extension === 'yaml'
|
|
30
|
-
) {
|
|
24
|
+
// `sourceCodeTypeOf` is the single answer to which extensions check-common
|
|
25
|
+
// parses; `.json` is the one addition, and only because `tcToSourceCode` models
|
|
26
|
+
// an unrecognised buffer as JSON for the editor (see its doc comment). Anything
|
|
27
|
+
// else is an asset, parsed — or not — by `graphParsers`.
|
|
28
|
+
if (sourceCodeTypeOf(uri) !== undefined || extension === 'json') {
|
|
31
29
|
return tcToSourceCode(uri, source);
|
|
32
30
|
}
|
|
33
31
|
|
|
34
|
-
const
|
|
35
|
-
|
|
32
|
+
const parse = graphParsers.extensions?.[extension];
|
|
33
|
+
const ast = (
|
|
34
|
+
parse ? parse(source, uri) : new Error('File parsing not implemented')
|
|
35
|
+
) as AssetSourceCode['ast'];
|
|
36
36
|
|
|
37
37
|
const assetSourceCode: AssetSourceCode = { type: 'asset', uri, source, ast };
|
|
38
38
|
return assetSourceCode;
|
package/src/types.ts
CHANGED
|
@@ -10,32 +10,62 @@ import {
|
|
|
10
10
|
GraphQLSourceCode,
|
|
11
11
|
YAMLSourceCode,
|
|
12
12
|
} from '@platformos/platformos-check-common';
|
|
13
|
+
import type { App, AppResolver } from '@platformos/platformos-common';
|
|
13
14
|
|
|
14
15
|
export interface IDependencies {
|
|
15
16
|
fs: CheckDependencies['fs'];
|
|
16
17
|
|
|
17
18
|
/** Optional perf improvement if you somehow have access to pre-computed source code info */
|
|
18
19
|
getSourceCode?: (uri: UriString) => Promise<FileSourceCode>;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The {@link App} whose per-type name index answers `{% render 'ui/card' %}`,
|
|
23
|
+
* `{% function … = 'commands/create' %}`, `{% graphql … = 'user/find' %}` and
|
|
24
|
+
* `layout: application` — the same object {@link IDependencies.getSourceCode} reads through
|
|
25
|
+
* when it is `appBackedGetSourceCode`.
|
|
26
|
+
*
|
|
27
|
+
* The pairing is the point: a caller that holds an App should hand it over for BOTH, so the
|
|
28
|
+
* graph reuses the lint's parses AND its index. Without it, `DocumentsLocator` falls back to a
|
|
29
|
+
* walk-only stand-in and every reference costs a `readDirectory` per candidate directory.
|
|
30
|
+
*
|
|
31
|
+
* It cannot change the ANSWER, only what it costs: `App.findOrLocate` resolves index-first and
|
|
32
|
+
* falls through to the very walk the stand-in performs. Omit it where there is genuinely no
|
|
33
|
+
* App: a build in a worker thread, a CLI, a fixture.
|
|
34
|
+
*/
|
|
35
|
+
app?: App | AppResolver;
|
|
19
36
|
}
|
|
20
37
|
|
|
21
|
-
|
|
38
|
+
/**
|
|
39
|
+
* `IDependencies` with the optional PERFORMANCE seams resolved by
|
|
40
|
+
* `augmentDependencies` — `getSourceCode` always present, `app` still optional
|
|
41
|
+
* because "no app here" is a legitimate final state, not an unaugmented one.
|
|
42
|
+
*/
|
|
43
|
+
export type Dependencies = Required<Omit<IDependencies, 'app'>> & Pick<IDependencies, 'app'>;
|
|
22
44
|
|
|
23
45
|
export type AugmentedDependencies = Dependencies;
|
|
24
46
|
|
|
47
|
+
/** Options controlling what a build/traversal computes beyond the edge graph. */
|
|
48
|
+
export interface GraphBuildOptions {
|
|
49
|
+
/**
|
|
50
|
+
* Populate each {@link LiquidModule.structural} (self-structural facts) during
|
|
51
|
+
* the build, as a by-product of the parse. OFF by default: a consumer that
|
|
52
|
+
* needs one buffer's structural facts should call `extractStructural` directly
|
|
53
|
+
* (the per-file primitive), so a full graph build — e.g. the LSP's, which never
|
|
54
|
+
* reads `structural` — does not compute a fact nothing consumes.
|
|
55
|
+
*/
|
|
56
|
+
includeStructural?: boolean;
|
|
57
|
+
}
|
|
58
|
+
|
|
25
59
|
export interface AppGraph {
|
|
26
60
|
rootUri: UriString;
|
|
27
61
|
entryPoints: AppModule[];
|
|
28
62
|
modules: Record<UriString, AppModule>;
|
|
29
63
|
}
|
|
30
64
|
|
|
31
|
-
export type AppModule = LiquidModule | AssetModule | GraphQLModule;
|
|
65
|
+
export type AppModule = LiquidModule | AssetModule | GraphQLModule | SchemaModule;
|
|
32
66
|
|
|
33
67
|
export type FileSourceCode =
|
|
34
|
-
|
|
|
35
|
-
| JSONSourceCode
|
|
36
|
-
| GraphQLSourceCode
|
|
37
|
-
| YAMLSourceCode
|
|
38
|
-
| AssetSourceCode;
|
|
68
|
+
LiquidSourceCode | JSONSourceCode | GraphQLSourceCode | YAMLSourceCode | AssetSourceCode;
|
|
39
69
|
|
|
40
70
|
export interface SerializableGraph {
|
|
41
71
|
rootUri: UriString;
|
|
@@ -49,8 +79,47 @@ export type SerializableEdge = Reference;
|
|
|
49
79
|
|
|
50
80
|
export type SerializableNode = Pick<AppModule, 'uri' | 'type' | 'kind' | 'exists'>;
|
|
51
81
|
|
|
82
|
+
/**
|
|
83
|
+
* A Liquid file's own structural declarations — a by-product of the parse the
|
|
84
|
+
* graph already does, so consumers need not re-parse the file.
|
|
85
|
+
*
|
|
86
|
+
* The usage arrays are ALWAYS present (sorted, de-duplicated): an empty array
|
|
87
|
+
* means "the file uses none", since the whole AST is analyzed — never "not
|
|
88
|
+
* extracted". The routing facts (`slug`/`layout`/`method`) are optional: an
|
|
89
|
+
* absent one means "not applicable / not declared" (e.g. a partial has no slug).
|
|
90
|
+
* `doc_params` lands in a later phase and will appear here when it does.
|
|
91
|
+
*/
|
|
92
|
+
export interface ModuleStructural {
|
|
93
|
+
/** Partial names rendered via `{% render %}` / `{% include %}` (the literal target names). */
|
|
94
|
+
renders_used: string[];
|
|
95
|
+
/** Operation names invoked via `{% graphql %}` (the literal target names). */
|
|
96
|
+
graphql_queries_used: string[];
|
|
97
|
+
/** Liquid filter names used anywhere in the file. */
|
|
98
|
+
filters_used: string[];
|
|
99
|
+
/** Liquid tag names used anywhere in the file. */
|
|
100
|
+
tags_used: string[];
|
|
101
|
+
/** Translation keys referenced via the `t` / `translate` filter. */
|
|
102
|
+
translation_keys: string[];
|
|
103
|
+
/** `@param` names declared in the file's `{% doc %}` block. */
|
|
104
|
+
doc_params: string[];
|
|
105
|
+
/**
|
|
106
|
+
* Effective URL slug: the frontmatter `slug` override if declared, else
|
|
107
|
+
* derived from the page's path. Present for page files only.
|
|
108
|
+
*/
|
|
109
|
+
slug?: string;
|
|
110
|
+
/** Declared wrapper layout (frontmatter `layout`), when declared. */
|
|
111
|
+
layout?: string;
|
|
112
|
+
/** Declared HTTP method (frontmatter `method`), when declared. */
|
|
113
|
+
method?: string;
|
|
114
|
+
}
|
|
115
|
+
|
|
52
116
|
export interface LiquidModule extends IAppModule<ModuleType.Liquid> {
|
|
53
117
|
kind: LiquidModuleKind;
|
|
118
|
+
/**
|
|
119
|
+
* The file's own structural declarations, populated during a full
|
|
120
|
+
* `buildAppGraph`. Absent when the file declares none of the surfaced facts.
|
|
121
|
+
*/
|
|
122
|
+
structural?: ModuleStructural;
|
|
54
123
|
}
|
|
55
124
|
|
|
56
125
|
export interface AssetModule extends IAppModule<ModuleType.Asset> {
|
|
@@ -64,6 +133,40 @@ export interface AssetModule extends IAppModule<ModuleType.Asset> {
|
|
|
64
133
|
*/
|
|
65
134
|
export interface GraphQLModule extends IAppModule<ModuleType.GraphQL> {
|
|
66
135
|
kind: 'graphql';
|
|
136
|
+
/**
|
|
137
|
+
* The platformOS model tables this operation targets (from its `table`
|
|
138
|
+
* filters / `record_create` inputs) — every distinct table in document order.
|
|
139
|
+
* Populated during `buildAppGraph` traversal; always present (empty = the
|
|
140
|
+
* operation declares no table, or was never resolved on disk).
|
|
141
|
+
*/
|
|
142
|
+
tables: string[];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* A custom model type / schema file (`custom_model_types`/`model_schemas`/
|
|
147
|
+
* `schema` dirs — `PlatformOSFileType.Table`). A platform primitive,
|
|
148
|
+
* modeled as a neutral graph node. A leaf node — schema files have no outgoing
|
|
149
|
+
* platformOS dependencies — and not render-reachable, so it is discovered and
|
|
150
|
+
* added explicitly during a full `buildAppGraph` (never an entry point).
|
|
151
|
+
*
|
|
152
|
+
* NOTE (ADR 004): this is a neutral platform fact. The commands/queries
|
|
153
|
+
* convention and resource/CRUD completeness that build ON schemas are NOT
|
|
154
|
+
* modeled here — they live in the convention overlay.
|
|
155
|
+
*/
|
|
156
|
+
export interface SchemaModule extends IAppModule<ModuleType.Schema> {
|
|
157
|
+
kind: 'schema';
|
|
158
|
+
/**
|
|
159
|
+
* The schema's top-level `name:`, when declared — one per schema, unlike a GraphQL op's
|
|
160
|
+
* {@link GraphQLModule.tables}. Absent when the file declares no `name:` or could not be
|
|
161
|
+
* parsed.
|
|
162
|
+
*
|
|
163
|
+
* NOT directly joinable against those tables for a MODULE schema. The platform queries one
|
|
164
|
+
* as `modules/<module>/<name>` (`ParameterizedName`, and `records_filter_input.rb` maps the
|
|
165
|
+
* GraphQL `table` to `parameterized_name`), so a consumer must put this through
|
|
166
|
+
* `parameterizedTableName` with the file's module first. Measured: joining the raw value
|
|
167
|
+
* reported every module table in a real project as missing.
|
|
168
|
+
*/
|
|
169
|
+
table?: string;
|
|
67
170
|
}
|
|
68
171
|
|
|
69
172
|
export interface IAppModule<T extends ModuleType> {
|
|
@@ -99,6 +202,7 @@ export const enum ModuleType {
|
|
|
99
202
|
Liquid = 'Liquid',
|
|
100
203
|
Asset = 'Asset',
|
|
101
204
|
GraphQL = 'GraphQL',
|
|
205
|
+
Schema = 'Schema',
|
|
102
206
|
}
|
|
103
207
|
|
|
104
208
|
export const enum LiquidModuleKind {
|
package/src/utils/index.ts
CHANGED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { Dependencies, Void, WebComponentMap } from './types';
|
|
2
|
-
/**
|
|
3
|
-
* Find all the web component definitions from the JavaScript files in the
|
|
4
|
-
* assets directory.
|
|
5
|
-
*
|
|
6
|
-
* From those, we'll be able to map `<custom-element-name>` to the definition in
|
|
7
|
-
* the corresponding asset file.
|
|
8
|
-
*/
|
|
9
|
-
export declare function getWebComponentMap(rootUri: string, { fs, getSourceCode }: Pick<Dependencies, 'fs' | 'getSourceCode'>): Promise<WebComponentMap>;
|
|
10
|
-
export declare function findWebComponentReferences(uri: string, assetRoot: string, getSourceCode: Dependencies['getSourceCode'], result: WebComponentMap): Promise<Void>;
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getWebComponentMap = getWebComponentMap;
|
|
4
|
-
exports.findWebComponentReferences = findWebComponentReferences;
|
|
5
|
-
const platformos_check_common_1 = require("@platformos/platformos-check-common");
|
|
6
|
-
const acorn_walk_1 = require("acorn-walk");
|
|
7
|
-
/**
|
|
8
|
-
* Regular expression for web component names
|
|
9
|
-
*
|
|
10
|
-
* Based on the HTML specification for valid custom element names.
|
|
11
|
-
*
|
|
12
|
-
* https://html.spec.whatwg.org/multipage/custom-elements.html#valid-custom-element-name
|
|
13
|
-
*/
|
|
14
|
-
const wcre = /^[a-z][-.\d_a-z\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD]*([-][-.\d_a-z\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD]*)$/u;
|
|
15
|
-
/**
|
|
16
|
-
* Find all the web component definitions from the JavaScript files in the
|
|
17
|
-
* assets directory.
|
|
18
|
-
*
|
|
19
|
-
* From those, we'll be able to map `<custom-element-name>` to the definition in
|
|
20
|
-
* the corresponding asset file.
|
|
21
|
-
*/
|
|
22
|
-
async function getWebComponentMap(rootUri, { fs, getSourceCode }) {
|
|
23
|
-
const webComponentDefs = new Map();
|
|
24
|
-
const assetRoot = platformos_check_common_1.path.join(rootUri, 'assets');
|
|
25
|
-
const jsFiles = await (0, platformos_check_common_1.recursiveReadDirectory)(fs, assetRoot, ([fileName]) => fileName.endsWith('.js'));
|
|
26
|
-
await Promise.all(jsFiles.map((uri) => findWebComponentReferences(uri, assetRoot, getSourceCode, webComponentDefs)));
|
|
27
|
-
return webComponentDefs;
|
|
28
|
-
}
|
|
29
|
-
async function findWebComponentReferences(uri, assetRoot, getSourceCode, result) {
|
|
30
|
-
const sourceCode = await getSourceCode(uri);
|
|
31
|
-
if (!uri.endsWith('.js')) {
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
const ast = sourceCode.ast;
|
|
35
|
-
if (ast instanceof Error) {
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
for (const node of ast.body) {
|
|
39
|
-
(0, acorn_walk_1.ancestor)(node, {
|
|
40
|
-
Literal(node, _state, ancestors) {
|
|
41
|
-
if (typeof node.value === 'string' && wcre.test(node.value)) {
|
|
42
|
-
// Making sure we're looking at customElements.define calls
|
|
43
|
-
const parentNode = ancestors.at(-2);
|
|
44
|
-
if (!parentNode)
|
|
45
|
-
return;
|
|
46
|
-
if (parentNode.type !== 'CallExpression')
|
|
47
|
-
return;
|
|
48
|
-
const callee = parentNode.callee;
|
|
49
|
-
if (callee.type !== 'MemberExpression')
|
|
50
|
-
return;
|
|
51
|
-
const property = callee.property;
|
|
52
|
-
if (property.type !== 'Identifier')
|
|
53
|
-
return;
|
|
54
|
-
if (property.name !== 'define')
|
|
55
|
-
return;
|
|
56
|
-
result.set(node.value, {
|
|
57
|
-
assetName: platformos_check_common_1.path.relative(uri, assetRoot),
|
|
58
|
-
range: [property.start, node.end],
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
return null;
|
|
62
|
-
},
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
//# sourceMappingURL=getWebComponentMap.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"getWebComponentMap.js","sourceRoot":"","sources":["../src/getWebComponentMap.ts"],"names":[],"mappings":";;AAsBA,gDAiBC;AAED,gEAuCC;AAhFD,iFAAmF;AACnF,2CAA+C;AAI/C;;;;;;GAMG;AACH,MAAM,IAAI,GACR,+UAA+U,CAAC;AAElV;;;;;;GAMG;AACI,KAAK,UAAU,kBAAkB,CACtC,OAAe,EACf,EAAE,EAAE,EAAE,aAAa,EAA8C;IAEjE,MAAM,gBAAgB,GAAoB,IAAI,GAAG,EAAE,CAAC;IACpD,MAAM,SAAS,GAAG,8BAAI,CAAC,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IAC/C,MAAM,OAAO,GAAG,MAAM,IAAA,gDAAsB,EAAC,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC,QAAQ,CAAC,EAAE,EAAE,CACzE,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,CACzB,CAAC;IAEF,MAAM,OAAO,CAAC,GAAG,CACf,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAClB,0BAA0B,CAAC,GAAG,EAAE,SAAS,EAAE,aAAa,EAAE,gBAAgB,CAAC,CAC5E,CACF,CAAC;IAEF,OAAO,gBAAgB,CAAC;AAC1B,CAAC;AAEM,KAAK,UAAU,0BAA0B,CAC9C,GAAW,EACX,SAAiB,EACjB,aAA4C,EAC5C,MAAuB;IAEvB,MAAM,UAAU,GAAG,MAAM,aAAa,CAAC,GAAG,CAAC,CAAC;IAC5C,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO;IACT,CAAC;IAED,MAAM,GAAG,GAAG,UAAU,CAAC,GAAG,CAAC;IAC3B,IAAI,GAAG,YAAY,KAAK,EAAE,CAAC;QACzB,OAAO;IACT,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAA,qBAAK,EAAC,IAAI,EAAE;YACV,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS;gBAC7B,IAAI,OAAO,IAAI,CAAC,KAAK,KAAK,QAAQ,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;oBAC5D,2DAA2D;oBAC3D,MAAM,UAAU,GAAG,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;oBACpC,IAAI,CAAC,UAAU;wBAAE,OAAO;oBACxB,IAAI,UAAU,CAAC,IAAI,KAAK,gBAAgB;wBAAE,OAAO;oBACjD,MAAM,MAAM,GAAI,UAA6B,CAAC,MAAM,CAAC;oBACrD,IAAI,MAAM,CAAC,IAAI,KAAK,kBAAkB;wBAAE,OAAO;oBAC/C,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC;oBACjC,IAAI,QAAQ,CAAC,IAAI,KAAK,YAAY;wBAAE,OAAO;oBAC3C,IAAI,QAAQ,CAAC,IAAI,KAAK,QAAQ;wBAAE,OAAO;oBAEvC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,EAAE;wBACrB,SAAS,EAAE,8BAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,SAAS,CAAC;wBACxC,KAAK,EAAE,CAAC,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC;qBAClC,CAAC,CAAC;gBACL,CAAC;gBACD,OAAO,IAAI,CAAC;YACd,CAAC;SACF,CAAC,CAAC;IACL,CAAC;AACH,CAAC"}
|