@xaendar/compiler 0.7.23 → 0.7.25
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/dist/xaendar-compiler.es.d.ts +1 -1
- package/dist/xaendar-compiler.es.js +157 -139
- package/package.json +3 -3
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* into the generated `adoptedStyleSheets` assignment.
|
|
12
12
|
* @returns A string containing the compiled Javascript render method body.
|
|
13
13
|
*/
|
|
14
|
-
export declare function compile(input: string,
|
|
14
|
+
export declare function compile(input: string, cssVariableName?: string): {
|
|
15
15
|
javascript: string;
|
|
16
16
|
typescript: string;
|
|
17
17
|
};
|
|
@@ -907,7 +907,7 @@ var Generator = class {
|
|
|
907
907
|
}
|
|
908
908
|
_processNode(node, parentNode, index, compilerContext, anchor) {
|
|
909
909
|
const state = this._states[node.type];
|
|
910
|
-
if (!state) throw new Error(`[
|
|
910
|
+
if (!state) throw new Error(`[Generator] No transition function for token type ${ASTNodeType[node.type]}`);
|
|
911
911
|
return state(node, parentNode, index, compilerContext, anchor);
|
|
912
912
|
}
|
|
913
913
|
};
|
|
@@ -2901,46 +2901,43 @@ var Parser = class {
|
|
|
2901
2901
|
};
|
|
2902
2902
|
//#endregion
|
|
2903
2903
|
//#region ../packages/compiler/src/type-checker/states/type-check-element.state.ts
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2904
|
+
/**
|
|
2905
|
+
* Type-checks an element node: its attribute/property bindings, its event
|
|
2906
|
+
* handlers, and recursively its children.
|
|
2907
|
+
*
|
|
2908
|
+
* No variable is declared for the element itself (see the module doc on
|
|
2909
|
+
* `TypeChecker` for why) — attribute expressions and event calls are
|
|
2910
|
+
* emitted as bare statements, validated in place.
|
|
2911
|
+
*/
|
|
2912
|
+
function typeCheckElement(node, context, index, processNode) {
|
|
2913
|
+
const lines = [];
|
|
2914
|
+
node.attributes.forEach(({ value }) => {
|
|
2915
|
+
if (typeof value !== "string") lines.push(`${resolveExpression(value.expression, context, { resolver: "root" })};`);
|
|
2912
2916
|
});
|
|
2913
2917
|
context.addUnresolvableIdentifier("$event");
|
|
2914
|
-
node.events.forEach(({
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
const resolvedParameter = resolveExpression(parameter, context, { resolver: "root" });
|
|
2918
|
-
if (!parsedEventParameter && resolvedParameter === "$event") {
|
|
2919
|
-
parsedEventParameter = true;
|
|
2920
|
-
return `$event`;
|
|
2921
|
-
} else return `${resolvedParameter}`;
|
|
2922
|
-
}).join(", ");
|
|
2923
|
-
const beginning = parsedEventParameter ? "($event)" : "()";
|
|
2924
|
-
retVal.code.push(`const ${nodeName}_${name} = ${beginning} => root.${handler}(${mappedParameters});`);
|
|
2918
|
+
node.events.forEach(({ handler, parameters }) => {
|
|
2919
|
+
const args = parameters.map((parameter) => resolveExpression(parameter, context, { resolver: "root" })).join(", ");
|
|
2920
|
+
lines.push(`root.${handler}(${args});`);
|
|
2925
2921
|
});
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
} });
|
|
2931
|
-
return retVal;
|
|
2922
|
+
node.children.forEach((child, i) => {
|
|
2923
|
+
lines.push(...processNode(child, context, i.toString()));
|
|
2924
|
+
});
|
|
2925
|
+
return lines;
|
|
2932
2926
|
}
|
|
2933
2927
|
//#endregion
|
|
2934
2928
|
//#region ../packages/compiler/src/type-checker/states/type-check-for.state.ts
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2929
|
+
/**
|
|
2930
|
+
* Type-checks an `@for` block using a real `for...of` loop.
|
|
2931
|
+
*
|
|
2932
|
+
* This replaces the previous "synthetic function with a `typeof array`
|
|
2933
|
+
* parameter" trick, which mistyped the loop variable as the *whole array*
|
|
2934
|
+
* rather than a single element. A real `for (const item of array)` lets
|
|
2935
|
+
* TypeScript infer `item`'s type correctly as the array's element type —
|
|
2936
|
+
* exactly like it would for a loop written by hand — with no synthetic
|
|
2937
|
+
* function boundary needed.
|
|
2938
|
+
*/
|
|
2939
|
+
function typeCheckFor(node, context, index, processNode) {
|
|
2940
|
+
const iterableExpr = context.hasIdentifier(node.iterableSource) ? node.iterableSource : `root.${node.iterableSource}`;
|
|
2944
2941
|
const indexName = resolveImplicit(node, "$index");
|
|
2945
2942
|
const firstName = resolveImplicit(node, "$first");
|
|
2946
2943
|
const lastName = resolveImplicit(node, "$last");
|
|
@@ -2953,22 +2950,20 @@ function typeCheckFor(node, parentNode, index, compilerContext) {
|
|
|
2953
2950
|
[lastName, "signal"],
|
|
2954
2951
|
[evenName, "signal"],
|
|
2955
2952
|
[oddName, "signal"]
|
|
2956
|
-
],
|
|
2957
|
-
const
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
}
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
})};`);
|
|
2971
|
-
return retVal;
|
|
2953
|
+
], context);
|
|
2954
|
+
const lines = [];
|
|
2955
|
+
lines.push(`for (const ${node.itemAlias} of ${iterableExpr}) {`);
|
|
2956
|
+
lines.push(...indent([
|
|
2957
|
+
`let ${indexName}!: number;`,
|
|
2958
|
+
`let ${firstName}!: boolean;`,
|
|
2959
|
+
`let ${lastName}!: boolean;`,
|
|
2960
|
+
`let ${evenName}!: boolean;`,
|
|
2961
|
+
`let ${oddName}!: boolean;`,
|
|
2962
|
+
`${resolveExpression(node.trackExpression, forContext, { resolver: "root" })};`,
|
|
2963
|
+
...node.children.flatMap((child, i) => processNode(child, forContext, i.toString()))
|
|
2964
|
+
]));
|
|
2965
|
+
lines.push("}");
|
|
2966
|
+
return lines;
|
|
2972
2967
|
}
|
|
2973
2968
|
/**
|
|
2974
2969
|
* Resolves the name that should be used in generated code for a given
|
|
@@ -2987,77 +2982,109 @@ function resolveImplicit(node, implicit) {
|
|
|
2987
2982
|
}
|
|
2988
2983
|
//#endregion
|
|
2989
2984
|
//#region ../packages/compiler/src/type-checker/states/type-check-if.state.ts
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
2985
|
+
/**
|
|
2986
|
+
* Type-checks an `@if`/`@else if`/`@else` chain using real TypeScript
|
|
2987
|
+
* `if` / `else if` / `else` blocks.
|
|
2988
|
+
*
|
|
2989
|
+
* This is a genuine correctness improvement over the previous
|
|
2990
|
+
* sibling-functions approach, not just a simplification: a real
|
|
2991
|
+
* `if`/`else if` chain gives the TS compiler's control-flow analysis the
|
|
2992
|
+
* negated narrowing of every preceding condition for free (e.g. inside an
|
|
2993
|
+
* `else if`, TS already knows the first condition was false) — something
|
|
2994
|
+
* flat sibling functions could never express.
|
|
2995
|
+
*/
|
|
2996
|
+
function typeCheckIf(node, context, index, processNode) {
|
|
2997
|
+
const lines = [];
|
|
2998
|
+
const condition = resolveExpression(node.conditionNode, context, { resolver: "root" });
|
|
2999
|
+
const ifContext = new CompilerContext([], context);
|
|
3000
|
+
lines.push(`if (${condition}) {`);
|
|
3001
|
+
lines.push(...indent(node.children.flatMap((child, i) => processNode(child, ifContext, i.toString()))));
|
|
3002
|
+
lines.push("}");
|
|
3003
3003
|
let alt = node.alternate;
|
|
3004
|
-
let i = 0;
|
|
3005
3004
|
while (alt?.type === ASTNodeType.ElseIf) {
|
|
3006
|
-
const
|
|
3007
|
-
const
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
node: alt,
|
|
3012
|
-
parentNode,
|
|
3013
|
-
context: elseIfContext
|
|
3014
|
-
} });
|
|
3005
|
+
const elseIfCondition = resolveExpression(alt.conditionNode, context, { resolver: "root" });
|
|
3006
|
+
const elseIfContext = new CompilerContext([], context);
|
|
3007
|
+
lines.push(`else if (${elseIfCondition}) {`);
|
|
3008
|
+
lines.push(...indent(alt.children.flatMap((child, i) => processNode(child, elseIfContext, i.toString()))));
|
|
3009
|
+
lines.push("}");
|
|
3015
3010
|
alt = alt.alternate;
|
|
3016
|
-
i++;
|
|
3017
3011
|
}
|
|
3018
3012
|
if (alt) {
|
|
3019
|
-
const elseContext = new CompilerContext([],
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
parentNode,
|
|
3024
|
-
context: elseContext
|
|
3025
|
-
} });
|
|
3013
|
+
const elseContext = new CompilerContext([], context);
|
|
3014
|
+
lines.push("else {");
|
|
3015
|
+
lines.push(...indent(alt.children.flatMap((child, i) => processNode(child, elseContext, i.toString()))));
|
|
3016
|
+
lines.push("}");
|
|
3026
3017
|
}
|
|
3027
|
-
return
|
|
3018
|
+
return lines;
|
|
3028
3019
|
}
|
|
3029
3020
|
//#endregion
|
|
3030
3021
|
//#region ../packages/compiler/src/type-checker/states/type-check-switch.state.ts
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3022
|
+
/**
|
|
3023
|
+
* Type-checks a `@switch` block using a real TypeScript `switch` statement.
|
|
3024
|
+
*
|
|
3025
|
+
* This drops the previous `const case_0: typeof switchExpr = 'literal';`
|
|
3026
|
+
* trick entirely: a real `switch (expr) { case 'literal': ... }` already
|
|
3027
|
+
* makes TS validate that each case value is assignable to the switch
|
|
3028
|
+
* expression's type as part of ordinary switch-statement semantics — and,
|
|
3029
|
+
* as a bonus, narrows the switch expression's type inside each case block
|
|
3030
|
+
* (e.g. from a `'loading' | 'error' | 'idle'` union down to just
|
|
3031
|
+
* `'loading'`), which the previous approach never provided.
|
|
3032
|
+
*
|
|
3033
|
+
* Multiple case labels that shared one body in the AST (fallthrough) are
|
|
3034
|
+
* emitted as stacked `case` labels sharing that same body, matching real
|
|
3035
|
+
* JS/TS fallthrough syntax directly.
|
|
3036
|
+
*/
|
|
3037
|
+
function typeCheckSwitch(node, context, index, processNode) {
|
|
3038
|
+
const lines = [`switch (${resolveExpression(node.expression, context, { resolver: "root" })}) {`];
|
|
3039
3039
|
node.children.forEach((caseNode, i) => {
|
|
3040
|
-
const caseContext = new CompilerContext([],
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
} });
|
|
3047
|
-
caseNode.condition?.forEach((condition, i) => retVal.code.push(`const ${caseKey}_${i}: typeof ${expression} = ${condition};`));
|
|
3040
|
+
const caseContext = new CompilerContext([], context);
|
|
3041
|
+
if (caseNode.condition?.length) caseNode.condition.forEach((conditionValue) => {
|
|
3042
|
+
lines.push(` case ${conditionValue}:`);
|
|
3043
|
+
});
|
|
3044
|
+
else lines.push(" default:");
|
|
3045
|
+
lines.push(...indent(indent([...caseNode.children.flatMap((child, ci) => processNode(child, caseContext, ci.toString())), "break;"])));
|
|
3048
3046
|
});
|
|
3049
|
-
|
|
3047
|
+
lines.push("}");
|
|
3048
|
+
return lines;
|
|
3050
3049
|
}
|
|
3051
3050
|
//#endregion
|
|
3052
3051
|
//#region ../packages/compiler/src/type-checker/states/type-check-text-and-interpolation.state.ts
|
|
3053
|
-
|
|
3054
|
-
|
|
3052
|
+
/**
|
|
3053
|
+
* Type-checks a text or interpolation node.
|
|
3054
|
+
*
|
|
3055
|
+
* Plain text nodes carry no expression, so they produce no lines. An
|
|
3056
|
+
* interpolation's expression is emitted as a bare statement — enough for
|
|
3057
|
+
* TS to validate it, with no name needing to be bound to the result.
|
|
3058
|
+
*/
|
|
3059
|
+
function typeCheckTextAndInterpolation(node, context, index, processNode) {
|
|
3060
|
+
return node.type === ASTNodeType.Interpolation ? [`${resolveExpression(node.expression, context, { resolver: "root" })};`] : [];
|
|
3055
3061
|
}
|
|
3056
3062
|
//#endregion
|
|
3057
3063
|
//#region ../packages/compiler/src/type-checker/type-checker.ts
|
|
3064
|
+
/**
|
|
3065
|
+
* Generates a single, flat TypeScript function body ("shim") from a
|
|
3066
|
+
* template AST, meant only to be fed to the TS compiler / LanguageService
|
|
3067
|
+
* for diagnostics — it is never executed and never emitted as real output.
|
|
3068
|
+
*
|
|
3069
|
+
* This deliberately does NOT mirror the JS code generator's structure:
|
|
3070
|
+
*
|
|
3071
|
+
* - No variable is declared per HTML element. Element identifiers exist in
|
|
3072
|
+
* the JS output purely so runtime code can create/reference the actual
|
|
3073
|
+
* DOM node; a type-check expression never references "the element
|
|
3074
|
+
* itself" (the DSL has no template-ref syntax), so an `HTMLElement`
|
|
3075
|
+
* local would add zero type-checking value.
|
|
3076
|
+
* - No control-flow block gets its own function. In the JS output, each
|
|
3077
|
+
* `@if`/`@for`/`@switch` becomes a separate function because it needs
|
|
3078
|
+
* its own runtime closure over the `Context` chain. The type checker has
|
|
3079
|
+
* no runtime at all, so real, nested TypeScript blocks — `if`, `for`,
|
|
3080
|
+
* `switch` — give correct scoping and (as a bonus) real control-flow
|
|
3081
|
+
* narrowing, for free, with no synthetic machinery.
|
|
3082
|
+
*
|
|
3083
|
+
* Every AST node turns directly into TypeScript lines, recursively, inside
|
|
3084
|
+
* one single `typeCheck()` function.
|
|
3085
|
+
*/
|
|
3058
3086
|
var TypeChecker = class {
|
|
3059
3087
|
_ast;
|
|
3060
|
-
_nodeToProcess = /* @__PURE__ */ new Map();
|
|
3061
3088
|
_states = {
|
|
3062
3089
|
[ASTNodeType.Text]: typeCheckTextAndInterpolation,
|
|
3063
3090
|
[ASTNodeType.Interpolation]: typeCheckTextAndInterpolation,
|
|
@@ -3070,43 +3097,34 @@ var TypeChecker = class {
|
|
|
3070
3097
|
constructor(_ast) {
|
|
3071
3098
|
this._ast = _ast;
|
|
3072
3099
|
}
|
|
3073
|
-
|
|
3074
|
-
|
|
3100
|
+
/**
|
|
3101
|
+
* Generates the full `function typeCheck() { ... }` shim body for the
|
|
3102
|
+
* component's template.
|
|
3103
|
+
*
|
|
3104
|
+
* A `let $event!: Event;` declaration is prepended only if the generated
|
|
3105
|
+
* body actually references `$event` (event handler bindings), so shims
|
|
3106
|
+
* for templates with no event bindings don't carry an unused local —
|
|
3107
|
+
* relevant if the consuming project has `noUnusedLocals` enabled.
|
|
3108
|
+
*/
|
|
3109
|
+
generate() {
|
|
3075
3110
|
const context = new CompilerContext();
|
|
3076
|
-
const
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
if (result) {
|
|
3083
|
-
const { code, functionsToProcess } = result;
|
|
3084
|
-
functionsToProcess?.forEach((value, key) => this._nodeToProcess.set(key, value));
|
|
3085
|
-
generatedCode.push(...indent(code));
|
|
3086
|
-
}
|
|
3087
|
-
}
|
|
3088
|
-
generatedCode.push("}");
|
|
3089
|
-
for (const [key, fnData] of this._nodeToProcess.entries()) {
|
|
3090
|
-
const { node, parentNode, precode, context } = fnData.fn;
|
|
3091
|
-
generatedCode.push(`\nfunction ${key}(${fnData.args?.join(", ") ?? ""}): void {`);
|
|
3092
|
-
if (precode) generatedCode.push(indent(precode));
|
|
3093
|
-
generatedCode.push(...indent([...node.children.map((child, i) => {
|
|
3094
|
-
const result = this._processNode(child, parentNode, i.toString(), context);
|
|
3095
|
-
if (result) {
|
|
3096
|
-
const { code, functionsToProcess } = result;
|
|
3097
|
-
functionsToProcess?.forEach((value, key) => this._nodeToProcess.set(key, value));
|
|
3098
|
-
return code;
|
|
3099
|
-
}
|
|
3100
|
-
return "";
|
|
3101
|
-
}).flat()]), "}");
|
|
3102
|
-
}
|
|
3103
|
-
return generatedCode.join("\n");
|
|
3111
|
+
const body = this._ast.flatMap((node, i) => this._processNode(node, context, i.toString()));
|
|
3112
|
+
return [
|
|
3113
|
+
"function typeCheck() {",
|
|
3114
|
+
...indent(body.some((line) => line.includes("$event")) ? ["let $event!: Event;", ...body] : body),
|
|
3115
|
+
"}"
|
|
3116
|
+
].join("\n");
|
|
3104
3117
|
}
|
|
3105
|
-
|
|
3118
|
+
/**
|
|
3119
|
+
* Dispatches a single AST node to its state function, passing itself
|
|
3120
|
+
* back down as `processNode` so state functions can recurse into their
|
|
3121
|
+
* own children inline.
|
|
3122
|
+
*/
|
|
3123
|
+
_processNode = (node, context, index) => {
|
|
3106
3124
|
const state = this._states[node.type];
|
|
3107
|
-
if (!state) throw new Error(`[
|
|
3108
|
-
return state(node,
|
|
3109
|
-
}
|
|
3125
|
+
if (!state) throw new Error(`[Type Checker] No transition function for token type ${ASTNodeType[node.type]}`);
|
|
3126
|
+
return state(node, context, index, this._processNode);
|
|
3127
|
+
};
|
|
3110
3128
|
};
|
|
3111
3129
|
//#endregion
|
|
3112
3130
|
//#region ../packages/compiler/src/compile.ts
|
|
@@ -3123,11 +3141,11 @@ var TypeChecker = class {
|
|
|
3123
3141
|
* into the generated `adoptedStyleSheets` assignment.
|
|
3124
3142
|
* @returns A string containing the compiled Javascript render method body.
|
|
3125
3143
|
*/
|
|
3126
|
-
function compile(input,
|
|
3144
|
+
function compile(input, cssVariableName) {
|
|
3127
3145
|
const nodes = new Parser(new Lexer(input).tokenize()).parse();
|
|
3128
3146
|
return {
|
|
3129
3147
|
javascript: new Generator(nodes).generate(cssVariableName),
|
|
3130
|
-
typescript: new TypeChecker(nodes).generate(
|
|
3148
|
+
typescript: new TypeChecker(nodes).generate()
|
|
3131
3149
|
};
|
|
3132
3150
|
}
|
|
3133
3151
|
//#endregion
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xaendar/compiler",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.25",
|
|
4
4
|
"description": "A library for transpiling Xaendar Templates into JavaScript code",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"type": "module",
|
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
}
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@xaendar/common": "0.7.
|
|
20
|
-
"@xaendar/types": "0.7.
|
|
19
|
+
"@xaendar/common": "0.7.25",
|
|
20
|
+
"@xaendar/types": "0.7.25",
|
|
21
21
|
"typescript": "^6.0.3"
|
|
22
22
|
}
|
|
23
23
|
}
|