@heshammesalam/heshjs 0.1.0 → 0.3.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/README.md +19 -4
- package/cli.js +26 -0
- package/dist/architecture/diagnostics.d.ts +12 -0
- package/dist/architecture/diagnostics.js +8 -0
- package/dist/architecture/diagnostics.js.map +1 -0
- package/dist/architecture/html.d.ts +16 -0
- package/dist/architecture/html.js +132 -0
- package/dist/architecture/html.js.map +1 -0
- package/dist/architecture/index.d.ts +42 -0
- package/dist/architecture/index.js +418 -0
- package/dist/architecture/index.js.map +1 -0
- package/dist/compiler/index.js +2 -0
- package/dist/compiler/index.js.map +1 -1
- package/dist/destinations/index.d.ts +63 -0
- package/dist/destinations/index.js +185 -0
- package/dist/destinations/index.js.map +1 -0
- package/dist/dom/index.d.ts +2 -0
- package/dist/dom/index.js +5 -1
- package/dist/dom/index.js.map +1 -1
- package/dist/domains/index.d.ts +24 -0
- package/dist/domains/index.js +110 -0
- package/dist/domains/index.js.map +1 -0
- package/dist/hmvc/index.d.ts +35 -0
- package/dist/hmvc/index.js +49 -0
- package/dist/hmvc/index.js.map +1 -0
- package/dist/parser/index.d.ts +15 -1
- package/dist/parser/index.js +73 -3
- package/dist/parser/index.js.map +1 -1
- package/dist/presentation/index.d.ts +9 -0
- package/dist/presentation/index.js +38 -0
- package/dist/presentation/index.js.map +1 -0
- package/dist/router/index.d.ts +24 -0
- package/dist/router/index.js +258 -0
- package/dist/router/index.js.map +1 -0
- package/dist/vite/index.d.ts +4 -1
- package/dist/vite/index.js +37 -2
- package/dist/vite/index.js.map +1 -1
- package/docs/contracts.md +60 -0
- package/docs/domain-graph.md +205 -0
- package/docs/grammar.md +4 -0
- package/docs/hmvc.md +134 -0
- package/package.json +10 -3
- package/src/architecture/diagnostics.ts +11 -0
- package/src/architecture/html.ts +88 -0
- package/src/architecture/index.ts +283 -0
- package/src/compiler/index.ts +1 -0
- package/src/destinations/index.ts +146 -0
- package/src/dom/index.ts +6 -1
- package/src/domains/index.ts +97 -0
- package/src/hmvc/index.ts +71 -0
- package/src/parser/index.ts +55 -4
- package/src/presentation/index.ts +31 -0
- package/src/router/index.ts +171 -0
- package/src/vite/index.ts +25 -2
package/README.md
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
# HeshJS
|
|
2
2
|
|
|
3
|
-
An independent JavaScript/TypeScript UI framework created by Hesham
|
|
4
|
-
|
|
3
|
+
An independent JavaScript/TypeScript UI framework created by Hesham, with History
|
|
4
|
+
API routing and hierarchical MVC modules plus an experimental Domain Graph layer.
|
|
5
|
+
|
|
6
|
+
Version 0.3 adds opt-in .hesh domain/presentation/destination contracts,
|
|
7
|
+
the Node-only `/architecture` compiler and `hesh check|graph` command, `/domains`,
|
|
8
|
+
`/presentation` and `/destinations` runtimes. See docs/contracts.md and
|
|
9
|
+
docs/domain-graph.md for the precise supported slice and its limits. The default
|
|
10
|
+
creator remains an HMVC starter; Domain Graph is enabled explicitly in Vite.
|
|
5
11
|
|
|
6
12
|
Requires Node.js 22.12 or later.
|
|
7
13
|
|
|
@@ -13,6 +19,9 @@ npm run dev
|
|
|
13
19
|
```
|
|
14
20
|
|
|
15
21
|
For TypeScript, use `npm create @heshammesalam/heshjs@latest my-app -- --ts`.
|
|
22
|
+
The starter is a working HMVC SPA with nested modules, Home/About/detail/not-found
|
|
23
|
+
routes and a persistent shell. Each module contains model, controller, View.hesh
|
|
24
|
+
and module registration. See `docs/hmvc.md` for APIs, lifecycle and deployment.
|
|
16
25
|
|
|
17
26
|
```hesh
|
|
18
27
|
export default view Counter {
|
|
@@ -28,16 +37,22 @@ to your Vite plugins. Import a default `.hesh` component, then call
|
|
|
28
37
|
`mount(Component, document.querySelector('#app'))` from `@heshammesalam/heshjs`.
|
|
29
38
|
|
|
30
39
|
ESM entrypoints: `@heshammesalam/heshjs`, `@heshammesalam/heshjs/reactivity`, `@heshammesalam/heshjs/dom`, `@heshammesalam/heshjs/parser`,
|
|
31
|
-
`@heshammesalam/heshjs/compiler`, `@heshammesalam/heshjs/vite
|
|
40
|
+
`@heshammesalam/heshjs/compiler`, `@heshammesalam/heshjs/vite`,
|
|
41
|
+
`@heshammesalam/heshjs/router`, `@heshammesalam/heshjs/hmvc`. JavaScript and declarations are included.
|
|
32
42
|
The root entry contains runtime APIs only; compiler/TypeScript code stays out of
|
|
33
43
|
the browser unless explicitly imported. Vite is an optional peer dependency.
|
|
34
44
|
|
|
35
45
|
State is shallow: replace arrays/objects to notify. Component instances own DOM,
|
|
36
46
|
effects and listener cleanup. Async handlers report errors but are not automatically
|
|
37
47
|
canceled on unmount. Hesh edits reload the page. See `docs/grammar.md` for supported
|
|
38
|
-
syntax. Props, conditional rendering, keyed lists, themes,
|
|
48
|
+
syntax. Props, conditional rendering, keyed lists, themes, SSR and full
|
|
39
49
|
type checking inside `.hesh` are not yet implemented. TypeScript helpers and
|
|
40
50
|
consumer APIs support strict type checking.
|
|
41
51
|
|
|
52
|
+
Router navigation intercepts eligible same-origin anchors and supports nested
|
|
53
|
+
outlets, :params, query/hash updates, back/forward and not-found handling. Parents
|
|
54
|
+
remain mounted across child navigation. Refreshing a deep link requires the host
|
|
55
|
+
to serve index.html for application paths; the scaffold documents that setup.
|
|
56
|
+
|
|
42
57
|
This package is published without granting an open-source license (`UNLICENSED`).
|
|
43
58
|
|
package/cli.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { checkProject, writeProject, formatDiagnostic } from './dist/architecture/index.js';
|
|
3
|
+
const args = process.argv.slice(2);
|
|
4
|
+
const command = args.shift();
|
|
5
|
+
if (command === '--help' || command === '-h' || !command) {
|
|
6
|
+
console.log('Usage: hesh check|graph [project-root] [--entry app.hesh] [--tsconfig tsconfig.json] [--write]');
|
|
7
|
+
process.exit(command ? 0 : 1);
|
|
8
|
+
}
|
|
9
|
+
if (!['check', 'graph'].includes(command)) { console.error(`Unknown command ${command}`); process.exit(1); }
|
|
10
|
+
const options = { root: process.cwd() }; let write = false;
|
|
11
|
+
if (args[0] && !args[0].startsWith('--')) options.root = args.shift();
|
|
12
|
+
while (args.length) {
|
|
13
|
+
const flag = args.shift();
|
|
14
|
+
if (flag === '--write') { write = true; continue; }
|
|
15
|
+
if (!['--entry', '--tsconfig'].includes(flag) || !args[0] || args[0].startsWith('--')) { console.error(`Invalid option ${flag}`); process.exit(1); }
|
|
16
|
+
options[flag.slice(2)] = args.shift();
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
const result = checkProject(options);
|
|
20
|
+
for (const item of result.diagnostics) console.error(formatDiagnostic(item));
|
|
21
|
+
if (result.diagnostics.some(item => item.severity === 'error')) process.exitCode = 1;
|
|
22
|
+
else {
|
|
23
|
+
if (write) writeProject(result);
|
|
24
|
+
console.log(command === 'graph' ? JSON.stringify(result.graph, null, 2) : `Architecture valid: ${result.graph.domains.length} domains, ${result.graph.destinations.length} destinations${write ? '; generated JS/declarations written' : ''}.`);
|
|
25
|
+
}
|
|
26
|
+
} catch (error) { console.error(error instanceof Error ? error.message : String(error)); process.exitCode = 1; }
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface ArchitectureDiagnostic {
|
|
2
|
+
code: string;
|
|
3
|
+
severity: 'error' | 'warning';
|
|
4
|
+
message: string;
|
|
5
|
+
file: string;
|
|
6
|
+
offset: number;
|
|
7
|
+
line: number;
|
|
8
|
+
column: number;
|
|
9
|
+
suggestion: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function diagnostic(code: string, message: string, file: string, source: string, offset: number, suggestion: string, severity?: 'error' | 'warning'): ArchitectureDiagnostic;
|
|
12
|
+
export declare function formatDiagnostic(item: ArchitectureDiagnostic): string;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export function diagnostic(code, message, file, source, offset, suggestion, severity = 'error') {
|
|
2
|
+
const lines = source.slice(0, offset).split('\n');
|
|
3
|
+
return { code, message, file, offset, line: lines.length, column: lines.at(-1).length + 1, suggestion, severity };
|
|
4
|
+
}
|
|
5
|
+
export function formatDiagnostic(item) {
|
|
6
|
+
return `${item.file}:${item.line}:${item.column} ${item.code} ${item.severity}: ${item.message}\n ${item.suggestion}`;
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=diagnostics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diagnostics.js","sourceRoot":"","sources":["../../src/architecture/diagnostics.ts"],"names":[],"mappings":"AAIA,MAAM,UAAU,UAAU,CAAC,IAAY,EAAE,OAAe,EAAE,IAAY,EAAE,MAAc,EAAE,MAAc,EAAE,UAAkB,EAAE,WAAgC,OAAO;IACjK,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAClD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAE,CAAC,MAAM,GAAG,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC;AACrH,CAAC;AACD,MAAM,UAAU,gBAAgB,CAAC,IAA4B;IAC3D,OAAO,GAAG,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,QAAQ,KAAK,IAAI,CAAC,OAAO,OAAO,IAAI,CAAC,UAAU,EAAE,CAAC;AACzH,CAAC","sourcesContent":["export interface ArchitectureDiagnostic {\n code: string; severity: 'error' | 'warning'; message: string;\n file: string; offset: number; line: number; column: number; suggestion: string;\n}\nexport function diagnostic(code: string, message: string, file: string, source: string, offset: number, suggestion: string, severity: 'error' | 'warning' = 'error'): ArchitectureDiagnostic {\n const lines = source.slice(0, offset).split('\\n');\n return { code, message, file, offset, line: lines.length, column: lines.at(-1)!.length + 1, suggestion, severity };\n}\nexport function formatDiagnostic(item: ArchitectureDiagnostic): string {\n return `${item.file}:${item.line}:${item.column} ${item.code} ${item.severity}: ${item.message}\\n ${item.suggestion}`;\n}\n"]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type ArchitectureDiagnostic } from './diagnostics.js';
|
|
2
|
+
export interface HTMLContract {
|
|
3
|
+
filename: string;
|
|
4
|
+
bindings: string[];
|
|
5
|
+
actions: string[];
|
|
6
|
+
destinations: Record<string, Record<string, string>>;
|
|
7
|
+
validateBinding?: (path: string, offset: number, expected?: string, writable?: boolean) => void;
|
|
8
|
+
}
|
|
9
|
+
export interface HTMLResult {
|
|
10
|
+
code: string;
|
|
11
|
+
diagnostics: ArchitectureDiagnostic[];
|
|
12
|
+
usedBindings: string[];
|
|
13
|
+
usedActions: string[];
|
|
14
|
+
destinations: string[];
|
|
15
|
+
}
|
|
16
|
+
export declare function compileHTML(source: string, contract: HTMLContract): HTMLResult;
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { parseFragment } from 'parse5';
|
|
2
|
+
import { diagnostic } from './diagnostics.js';
|
|
3
|
+
export function compileHTML(source, contract) {
|
|
4
|
+
const diagnostics = [], usedBindings = new Set(), usedActions = new Set();
|
|
5
|
+
const report = (code, message, at, suggestion, severity = 'error') => diagnostics.push(diagnostic(code, message, contract.filename, source, at, suggestion, severity));
|
|
6
|
+
const fragment = parseFragment(source, { sourceCodeLocationInfo: true, onParseError: error => report('HESH-HTML-001', error.code, error.startOffset, 'Correct the HTML syntax.') });
|
|
7
|
+
const lines = ["import * as p from '@heshammesalam/heshjs/presentation';", 'export default function render(parent, state, destinations) {'];
|
|
8
|
+
let serial = 0;
|
|
9
|
+
const linked = new Set();
|
|
10
|
+
const path = (value, offset, expected, writable = false) => {
|
|
11
|
+
const pieces = value.trim().split('.');
|
|
12
|
+
if (pieces.some(piece => !/^[$_\p{ID_Start}][$_\u200c\u200d\p{ID_Continue}]*$/u.test(piece) || ['constructor', '__proto__', 'prototype'].includes(piece)))
|
|
13
|
+
report('HESH-BIND-001', `Invalid binding path ${value}`, offset, 'Use a declared dotted data path, not an expression.');
|
|
14
|
+
if (!contract.bindings.includes(pieces[0]))
|
|
15
|
+
report('HESH-BIND-002', `Missing data binding ${pieces[0]}`, offset, 'Declare the binding and return it from the presentation factory.');
|
|
16
|
+
usedBindings.add(pieces[0]);
|
|
17
|
+
contract.validateBinding?.(value.trim(), offset, expected, writable);
|
|
18
|
+
return `p.readBinding(state.bindings, ${JSON.stringify(value.trim())})`;
|
|
19
|
+
};
|
|
20
|
+
const interpolation = (value, offset) => {
|
|
21
|
+
const parts = [];
|
|
22
|
+
let literal = '', cursor = 0;
|
|
23
|
+
const emit = () => { if (literal) {
|
|
24
|
+
parts.push(JSON.stringify(literal));
|
|
25
|
+
literal = '';
|
|
26
|
+
} };
|
|
27
|
+
while (cursor < value.length) {
|
|
28
|
+
if (value[cursor] === '\\' && ['{', '}'].includes(value[cursor + 1] ?? '')) {
|
|
29
|
+
literal += value[cursor + 1];
|
|
30
|
+
cursor += 2;
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (value[cursor] !== '{') {
|
|
34
|
+
literal += value[cursor++];
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
emit();
|
|
38
|
+
const start = ++cursor, end = value.indexOf('}', start);
|
|
39
|
+
if (end < 0) {
|
|
40
|
+
report('HESH-BIND-001', 'Unterminated interpolation', offset + start - 1, 'Close the binding with }.');
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
parts.push(`String(${path(value.slice(start, end), offset + start)} ?? '')`);
|
|
44
|
+
cursor = end + 1;
|
|
45
|
+
}
|
|
46
|
+
emit();
|
|
47
|
+
return parts.join(' + ') || "''";
|
|
48
|
+
};
|
|
49
|
+
const visit = (node, parent) => {
|
|
50
|
+
const at = node.sourceCodeLocation?.startOffset ?? 0;
|
|
51
|
+
if (node.nodeName === '#text') {
|
|
52
|
+
lines.push(`p.text(${parent}, () => ${interpolation(node.value, at)});`);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (!('tagName' in node))
|
|
56
|
+
return;
|
|
57
|
+
if (node.namespaceURI !== 'http://www.w3.org/1999/xhtml' || ['script', 'iframe', 'object', 'embed', 'base', 'meta', 'link', 'style', 'template'].includes(node.tagName)) {
|
|
58
|
+
report('HESH-HTML-002', `Unsupported active/foreign template element <${node.tagName}>`, at, 'Use semantic HTML and declared CSS/JS modules.');
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const id = `node${serial++}`;
|
|
62
|
+
lines.push(`const ${id} = p.element(${parent}, ${JSON.stringify(node.tagName)});`);
|
|
63
|
+
const attributes = Object.fromEntries(node.attrs.map(attr => [attr.name, attr.value]));
|
|
64
|
+
const attrAt = (name) => node.sourceCodeLocation?.attrs?.[name]?.startOffset ?? at;
|
|
65
|
+
for (const attr of node.attrs) {
|
|
66
|
+
if (attr.name.startsWith('h-'))
|
|
67
|
+
continue;
|
|
68
|
+
if (/^on/i.test(attr.name) || ['srcdoc', 'innerhtml', 'outerhtml'].includes(attr.name)) {
|
|
69
|
+
report('HESH-HTML-003', `Unsafe attribute ${attr.name}`, attrAt(attr.name), 'Use h-action and safe DOM bindings.');
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
const wholeBinding = attr.value.startsWith('{') && attr.value.endsWith('}') && !attr.value.slice(1, -1).includes('{') && !attr.value.slice(1, -1).includes('}');
|
|
73
|
+
lines.push(`p.attribute(${id}, ${JSON.stringify(attr.name)}, () => ${wholeBinding ? path(attr.value.slice(1, -1), attrAt(attr.name) + 1) : interpolation(attr.value, attrAt(attr.name))});`);
|
|
74
|
+
}
|
|
75
|
+
for (const name of Object.keys(attributes))
|
|
76
|
+
if (name.startsWith('h-') && !['h-action', 'h-bind', 'h-value', 'h-destination'].includes(name) && !name.startsWith('h-input-'))
|
|
77
|
+
report('HESH-HTML-004', `Unsupported directive ${name}`, attrAt(name), 'Use supported directives; keyed lists/scoped templates are a later milestone.');
|
|
78
|
+
if (attributes['h-bind'] !== undefined) {
|
|
79
|
+
if (node.childNodes.length)
|
|
80
|
+
report('HESH-BIND-003', 'h-bind cannot also contain children', at, 'Remove the children or use text interpolation.');
|
|
81
|
+
lines.push(`p.text(${id}, () => ${path(attributes['h-bind'], attrAt('h-bind'))});`);
|
|
82
|
+
}
|
|
83
|
+
else
|
|
84
|
+
node.childNodes.forEach(child => visit(child, id));
|
|
85
|
+
if (attributes['h-action'] !== undefined) {
|
|
86
|
+
const name = attributes['h-action'];
|
|
87
|
+
usedActions.add(name);
|
|
88
|
+
if (!contract.actions.includes(name))
|
|
89
|
+
report('HESH-ACTION-001', `Missing action ${name}`, attrAt('h-action'), 'Declare and implement the action.');
|
|
90
|
+
lines.push(`p.bindAction(${id}, state, ${JSON.stringify(name)});`);
|
|
91
|
+
}
|
|
92
|
+
if (attributes['h-value'] !== undefined) {
|
|
93
|
+
const name = attributes['h-value'];
|
|
94
|
+
if (!['input', 'textarea', 'select'].includes(node.tagName) || name.includes('.') || ['checkbox', 'radio', 'file'].includes(attributes.type ?? '') || attributes.multiple !== undefined)
|
|
95
|
+
report('HESH-BIND-004', 'h-value requires a single-value text control and root writable signal', attrAt('h-value'), 'Use a text input/textarea/single select and a declared signal.');
|
|
96
|
+
path(name, attrAt('h-value'), undefined, true);
|
|
97
|
+
lines.push(`p.bindValue(${id}, state.bindings, ${JSON.stringify(name)});`);
|
|
98
|
+
}
|
|
99
|
+
const destination = attributes['h-destination'];
|
|
100
|
+
const inputs = Object.keys(attributes).filter(name => name.startsWith('h-input-'));
|
|
101
|
+
if (destination !== undefined) {
|
|
102
|
+
linked.add(destination);
|
|
103
|
+
const schema = contract.destinations[destination];
|
|
104
|
+
if (!schema)
|
|
105
|
+
report('HESH-NAV-001', `Unknown destination ${destination}`, attrAt('h-destination'), 'Declare this destination in the application.');
|
|
106
|
+
if (node.tagName !== 'a' || attributes.href !== undefined)
|
|
107
|
+
report('HESH-NAV-002', 'h-destination requires an anchor without an explicit href', at, 'Use <a h-destination="Name" ...>.');
|
|
108
|
+
for (const key of Object.keys(schema ?? {}))
|
|
109
|
+
if (attributes[`h-input-${key.toLowerCase()}`] === undefined)
|
|
110
|
+
report('HESH-NAV-003', `Missing destination input ${key}`, at, `Add h-input-${key} bound to a declared value.`);
|
|
111
|
+
const entries = inputs.map(attr => {
|
|
112
|
+
const key = Object.keys(schema ?? {}).find(key => key.toLowerCase() === attr.slice(8)) ?? attr.slice(8);
|
|
113
|
+
if (!schema || !Object.hasOwn(schema, key))
|
|
114
|
+
report('HESH-NAV-003', `Unknown destination input ${key}`, attrAt(attr), 'Use only inputs declared by the destination.');
|
|
115
|
+
return `${JSON.stringify(key)}: ${path(attributes[attr], attrAt(attr), schema?.[key])}`;
|
|
116
|
+
});
|
|
117
|
+
lines.push(`p.attribute(${id}, 'href', () => destinations[${JSON.stringify(destination)}].href({${entries.join(',')}}));`);
|
|
118
|
+
}
|
|
119
|
+
else if (inputs.length)
|
|
120
|
+
report('HESH-NAV-003', 'h-input-* requires h-destination', at, 'Declare the destination on this anchor.');
|
|
121
|
+
};
|
|
122
|
+
fragment.childNodes.forEach(node => visit(node, 'parent'));
|
|
123
|
+
lines.push('}');
|
|
124
|
+
for (const name of contract.bindings)
|
|
125
|
+
if (!usedBindings.has(name))
|
|
126
|
+
report('HESH-BIND-005', `Unused binding ${name}`, 0, 'Remove the declaration if it is not needed by this template.', 'warning');
|
|
127
|
+
for (const name of contract.actions)
|
|
128
|
+
if (!usedActions.has(name))
|
|
129
|
+
report('HESH-ACTION-002', `Unused action ${name}`, 0, 'Remove the declaration if it is not needed by this template.', 'warning');
|
|
130
|
+
return { code: lines.join('\n'), diagnostics, usedBindings: [...usedBindings], usedActions: [...usedActions], destinations: [...linked] };
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=html.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"html.js","sourceRoot":"","sources":["../../src/architecture/html.ts"],"names":[],"mappings":"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","sourcesContent":["import { parseFragment, type DefaultTreeAdapterMap } from 'parse5';\nimport { diagnostic, type ArchitectureDiagnostic } from './diagnostics.js';\nexport interface HTMLContract {\n filename: string; bindings: string[]; actions: string[];\n destinations: Record<string, Record<string, string>>;\n validateBinding?: (path: string, offset: number, expected?: string, writable?: boolean) => void;\n}\nexport interface HTMLResult { code: string; diagnostics: ArchitectureDiagnostic[]; usedBindings: string[]; usedActions: string[]; destinations: string[] }\nexport function compileHTML(source: string, contract: HTMLContract): HTMLResult {\n const diagnostics: ArchitectureDiagnostic[] = [], usedBindings = new Set<string>(), usedActions = new Set<string>();\n const report = (code: string, message: string, at: number, suggestion: string, severity: 'error' | 'warning' = 'error') => diagnostics.push(diagnostic(code, message, contract.filename, source, at, suggestion, severity));\n const fragment = parseFragment(source, { sourceCodeLocationInfo: true, onParseError: error => report('HESH-HTML-001', error.code, error.startOffset, 'Correct the HTML syntax.') });\n const lines = [\"import * as p from '@heshammesalam/heshjs/presentation';\", 'export default function render(parent, state, destinations) {'];\n let serial = 0;\n const linked = new Set<string>();\n const path = (value: string, offset: number, expected?: string, writable = false): string => {\n const pieces = value.trim().split('.');\n if (pieces.some(piece => !/^[$_\\p{ID_Start}][$_\\u200c\\u200d\\p{ID_Continue}]*$/u.test(piece) || ['constructor', '__proto__', 'prototype'].includes(piece))) report('HESH-BIND-001', `Invalid binding path ${value}`, offset, 'Use a declared dotted data path, not an expression.');\n if (!contract.bindings.includes(pieces[0]!)) report('HESH-BIND-002', `Missing data binding ${pieces[0]}`, offset, 'Declare the binding and return it from the presentation factory.');\n usedBindings.add(pieces[0]!); contract.validateBinding?.(value.trim(), offset, expected, writable);\n return `p.readBinding(state.bindings, ${JSON.stringify(value.trim())})`;\n };\n const interpolation = (value: string, offset: number): string => {\n const parts: string[] = []; let literal = '', cursor = 0;\n const emit = () => { if (literal) { parts.push(JSON.stringify(literal)); literal = ''; } };\n while (cursor < value.length) {\n if (value[cursor] === '\\\\' && ['{', '}'].includes(value[cursor + 1] ?? '')) { literal += value[cursor + 1]; cursor += 2; continue; }\n if (value[cursor] !== '{') { literal += value[cursor++]; continue; }\n emit(); const start = ++cursor, end = value.indexOf('}', start);\n if (end < 0) { report('HESH-BIND-001', 'Unterminated interpolation', offset + start - 1, 'Close the binding with }.'); break; }\n parts.push(`String(${path(value.slice(start, end), offset + start)} ?? '')`); cursor = end + 1;\n }\n emit(); return parts.join(' + ') || \"''\";\n };\n const visit = (node: DefaultTreeAdapterMap['childNode'], parent: string): void => {\n const at = node.sourceCodeLocation?.startOffset ?? 0;\n if (node.nodeName === '#text') { lines.push(`p.text(${parent}, () => ${interpolation((node as DefaultTreeAdapterMap['textNode']).value, at)});`); return; }\n if (!('tagName' in node)) return;\n if (node.namespaceURI !== 'http://www.w3.org/1999/xhtml' || ['script', 'iframe', 'object', 'embed', 'base', 'meta', 'link', 'style', 'template'].includes(node.tagName)) {\n report('HESH-HTML-002', `Unsupported active/foreign template element <${node.tagName}>`, at, 'Use semantic HTML and declared CSS/JS modules.'); return;\n }\n const id = `node${serial++}`;\n lines.push(`const ${id} = p.element(${parent}, ${JSON.stringify(node.tagName)});`);\n const attributes = Object.fromEntries(node.attrs.map(attr => [attr.name, attr.value]));\n const attrAt = (name: string) => node.sourceCodeLocation?.attrs?.[name]?.startOffset ?? at;\n for (const attr of node.attrs) {\n if (attr.name.startsWith('h-')) continue;\n if (/^on/i.test(attr.name) || ['srcdoc', 'innerhtml', 'outerhtml'].includes(attr.name)) { report('HESH-HTML-003', `Unsafe attribute ${attr.name}`, attrAt(attr.name), 'Use h-action and safe DOM bindings.'); continue; }\n const wholeBinding = attr.value.startsWith('{') && attr.value.endsWith('}') && !attr.value.slice(1, -1).includes('{') && !attr.value.slice(1, -1).includes('}');\n lines.push(`p.attribute(${id}, ${JSON.stringify(attr.name)}, () => ${wholeBinding ? path(attr.value.slice(1, -1), attrAt(attr.name) + 1) : interpolation(attr.value, attrAt(attr.name))});`);\n }\n for (const name of Object.keys(attributes)) if (name.startsWith('h-') && !['h-action', 'h-bind', 'h-value', 'h-destination'].includes(name) && !name.startsWith('h-input-')) report('HESH-HTML-004', `Unsupported directive ${name}`, attrAt(name), 'Use supported directives; keyed lists/scoped templates are a later milestone.');\n if (attributes['h-bind'] !== undefined) {\n if (node.childNodes.length) report('HESH-BIND-003', 'h-bind cannot also contain children', at, 'Remove the children or use text interpolation.');\n lines.push(`p.text(${id}, () => ${path(attributes['h-bind'], attrAt('h-bind'))});`);\n } else node.childNodes.forEach(child => visit(child, id));\n if (attributes['h-action'] !== undefined) {\n const name = attributes['h-action']; usedActions.add(name);\n if (!contract.actions.includes(name)) report('HESH-ACTION-001', `Missing action ${name}`, attrAt('h-action'), 'Declare and implement the action.');\n lines.push(`p.bindAction(${id}, state, ${JSON.stringify(name)});`);\n }\n if (attributes['h-value'] !== undefined) {\n const name = attributes['h-value'];\n if (!['input', 'textarea', 'select'].includes(node.tagName) || name.includes('.') || ['checkbox', 'radio', 'file'].includes(attributes.type ?? '') || attributes.multiple !== undefined) report('HESH-BIND-004', 'h-value requires a single-value text control and root writable signal', attrAt('h-value'), 'Use a text input/textarea/single select and a declared signal.');\n path(name, attrAt('h-value'), undefined, true);\n lines.push(`p.bindValue(${id}, state.bindings, ${JSON.stringify(name)});`);\n }\n const destination = attributes['h-destination'];\n const inputs = Object.keys(attributes).filter(name => name.startsWith('h-input-'));\n if (destination !== undefined) {\n linked.add(destination);\n const schema = contract.destinations[destination];\n if (!schema) report('HESH-NAV-001', `Unknown destination ${destination}`, attrAt('h-destination'), 'Declare this destination in the application.');\n if (node.tagName !== 'a' || attributes.href !== undefined) report('HESH-NAV-002', 'h-destination requires an anchor without an explicit href', at, 'Use <a h-destination=\"Name\" ...>.');\n for (const key of Object.keys(schema ?? {})) if (attributes[`h-input-${key.toLowerCase()}`] === undefined) report('HESH-NAV-003', `Missing destination input ${key}`, at, `Add h-input-${key} bound to a declared value.`);\n const entries = inputs.map(attr => {\n const key = Object.keys(schema ?? {}).find(key => key.toLowerCase() === attr.slice(8)) ?? attr.slice(8);\n if (!schema || !Object.hasOwn(schema, key)) report('HESH-NAV-003', `Unknown destination input ${key}`, attrAt(attr), 'Use only inputs declared by the destination.');\n return `${JSON.stringify(key)}: ${path(attributes[attr]!, attrAt(attr), schema?.[key])}`;\n });\n lines.push(`p.attribute(${id}, 'href', () => destinations[${JSON.stringify(destination)}].href({${entries.join(',')}}));`);\n } else if (inputs.length) report('HESH-NAV-003', 'h-input-* requires h-destination', at, 'Declare the destination on this anchor.');\n };\n fragment.childNodes.forEach(node => visit(node, 'parent')); lines.push('}');\n for (const name of contract.bindings) if (!usedBindings.has(name)) report('HESH-BIND-005', `Unused binding ${name}`, 0, 'Remove the declaration if it is not needed by this template.', 'warning');\n for (const name of contract.actions) if (!usedActions.has(name)) report('HESH-ACTION-002', `Unused action ${name}`, 0, 'Remove the declaration if it is not needed by this template.', 'warning');\n return { code: lines.join('\\n'), diagnostics, usedBindings: [...usedBindings], usedActions: [...usedActions], destinations: [...linked] };\n}\n"]}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { compileHTML } from './html.js';
|
|
2
|
+
import { formatDiagnostic, type ArchitectureDiagnostic } from './diagnostics.js';
|
|
3
|
+
export { compileHTML, formatDiagnostic };
|
|
4
|
+
export type { ArchitectureDiagnostic };
|
|
5
|
+
export interface ProjectOptions {
|
|
6
|
+
root: string;
|
|
7
|
+
entry?: string;
|
|
8
|
+
tsconfig?: string;
|
|
9
|
+
outDir?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface ProjectResult {
|
|
12
|
+
diagnostics: ArchitectureDiagnostic[];
|
|
13
|
+
files: string[];
|
|
14
|
+
outputs: Record<string, string>;
|
|
15
|
+
graph: {
|
|
16
|
+
domains: {
|
|
17
|
+
name: string;
|
|
18
|
+
exposes: string[];
|
|
19
|
+
consumes: string[];
|
|
20
|
+
}[];
|
|
21
|
+
destinations: {
|
|
22
|
+
name: string;
|
|
23
|
+
url: string;
|
|
24
|
+
input: Record<string, string>;
|
|
25
|
+
owner: string;
|
|
26
|
+
}[];
|
|
27
|
+
links: {
|
|
28
|
+
from: string;
|
|
29
|
+
to: string;
|
|
30
|
+
}[];
|
|
31
|
+
};
|
|
32
|
+
statistics: {
|
|
33
|
+
parsed: number;
|
|
34
|
+
reused: number;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
export declare function createArchitectureCompiler(): {
|
|
38
|
+
clear(): void;
|
|
39
|
+
check(options: ProjectOptions): ProjectResult;
|
|
40
|
+
};
|
|
41
|
+
export declare function checkProject(options: ProjectOptions): ProjectResult;
|
|
42
|
+
export declare function writeProject(result: ProjectResult): void;
|