@xaendar/compiler 0.7.24 → 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 -157
- 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,49 +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
|
-
type: "HTMLElement"
|
|
2931
|
-
},
|
|
2932
|
-
context
|
|
2933
|
-
} });
|
|
2934
|
-
return retVal;
|
|
2922
|
+
node.children.forEach((child, i) => {
|
|
2923
|
+
lines.push(...processNode(child, context, i.toString()));
|
|
2924
|
+
});
|
|
2925
|
+
return lines;
|
|
2935
2926
|
}
|
|
2936
2927
|
//#endregion
|
|
2937
2928
|
//#region ../packages/compiler/src/type-checker/states/type-check-for.state.ts
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
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}`;
|
|
2947
2941
|
const indexName = resolveImplicit(node, "$index");
|
|
2948
2942
|
const firstName = resolveImplicit(node, "$first");
|
|
2949
2943
|
const lastName = resolveImplicit(node, "$last");
|
|
@@ -2956,25 +2950,20 @@ function typeCheckFor(node, parentNode, index, compilerContext) {
|
|
|
2956
2950
|
[lastName, "signal"],
|
|
2957
2951
|
[evenName, "signal"],
|
|
2958
2952
|
[oddName, "signal"]
|
|
2959
|
-
],
|
|
2960
|
-
const
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
});
|
|
2972
|
-
|
|
2973
|
-
retVal.code.push(`const ${forKey}_${node.itemAlias} = ${resolveExpression(node.trackExpression, forContext, {
|
|
2974
|
-
skipResolution: true,
|
|
2975
|
-
resolver: `${iterableExpr}`
|
|
2976
|
-
})};`);
|
|
2977
|
-
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;
|
|
2978
2967
|
}
|
|
2979
2968
|
/**
|
|
2980
2969
|
* Resolves the name that should be used in generated code for a given
|
|
@@ -2993,89 +2982,109 @@ function resolveImplicit(node, implicit) {
|
|
|
2993
2982
|
}
|
|
2994
2983
|
//#endregion
|
|
2995
2984
|
//#region ../packages/compiler/src/type-checker/states/type-check-if.state.ts
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
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("}");
|
|
3012
3003
|
let alt = node.alternate;
|
|
3013
|
-
let i = 0;
|
|
3014
3004
|
while (alt?.type === ASTNodeType.ElseIf) {
|
|
3015
|
-
const
|
|
3016
|
-
const
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
node: alt,
|
|
3021
|
-
parentNode: {
|
|
3022
|
-
identifier: keyElseIf,
|
|
3023
|
-
type: "HTMLElement"
|
|
3024
|
-
},
|
|
3025
|
-
context: elseIfContext
|
|
3026
|
-
} });
|
|
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("}");
|
|
3027
3010
|
alt = alt.alternate;
|
|
3028
|
-
i++;
|
|
3029
3011
|
}
|
|
3030
3012
|
if (alt) {
|
|
3031
|
-
const elseContext = new CompilerContext([],
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
parentNode: {
|
|
3036
|
-
identifier: getBlockIdentifier("else", parentNode.identifier, index),
|
|
3037
|
-
type: "HTMLElement"
|
|
3038
|
-
},
|
|
3039
|
-
context: elseContext
|
|
3040
|
-
} });
|
|
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("}");
|
|
3041
3017
|
}
|
|
3042
|
-
return
|
|
3018
|
+
return lines;
|
|
3043
3019
|
}
|
|
3044
3020
|
//#endregion
|
|
3045
3021
|
//#region ../packages/compiler/src/type-checker/states/type-check-switch.state.ts
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
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" })}) {`];
|
|
3054
3039
|
node.children.forEach((caseNode, i) => {
|
|
3055
|
-
const caseContext = new CompilerContext([],
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
type: "HTMLElement"
|
|
3062
|
-
},
|
|
3063
|
-
context: caseContext
|
|
3064
|
-
} });
|
|
3065
|
-
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;"])));
|
|
3066
3046
|
});
|
|
3067
|
-
|
|
3047
|
+
lines.push("}");
|
|
3048
|
+
return lines;
|
|
3068
3049
|
}
|
|
3069
3050
|
//#endregion
|
|
3070
3051
|
//#region ../packages/compiler/src/type-checker/states/type-check-text-and-interpolation.state.ts
|
|
3071
|
-
|
|
3072
|
-
|
|
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" })};`] : [];
|
|
3073
3061
|
}
|
|
3074
3062
|
//#endregion
|
|
3075
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
|
+
*/
|
|
3076
3086
|
var TypeChecker = class {
|
|
3077
3087
|
_ast;
|
|
3078
|
-
_nodeToProcess = /* @__PURE__ */ new Map();
|
|
3079
3088
|
_states = {
|
|
3080
3089
|
[ASTNodeType.Text]: typeCheckTextAndInterpolation,
|
|
3081
3090
|
[ASTNodeType.Interpolation]: typeCheckTextAndInterpolation,
|
|
@@ -3088,43 +3097,34 @@ var TypeChecker = class {
|
|
|
3088
3097
|
constructor(_ast) {
|
|
3089
3098
|
this._ast = _ast;
|
|
3090
3099
|
}
|
|
3091
|
-
|
|
3092
|
-
|
|
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() {
|
|
3093
3110
|
const context = new CompilerContext();
|
|
3094
|
-
const
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
if (result) {
|
|
3101
|
-
const { code, functionsToProcess } = result;
|
|
3102
|
-
functionsToProcess?.forEach((value, key) => this._nodeToProcess.set(key, value));
|
|
3103
|
-
generatedCode.push(...indent(code));
|
|
3104
|
-
}
|
|
3105
|
-
}
|
|
3106
|
-
generatedCode.push("}");
|
|
3107
|
-
for (const [key, fnData] of this._nodeToProcess.entries()) {
|
|
3108
|
-
const { node, parentNode, precode, context } = fnData.fn;
|
|
3109
|
-
generatedCode.push(`\nfunction ${key}(${fnData.args?.join(", ") ?? ""}): void {`);
|
|
3110
|
-
if (precode) generatedCode.push(indent(precode));
|
|
3111
|
-
generatedCode.push(...indent([...node.children.map((child, i) => {
|
|
3112
|
-
const result = this._processNode(child, parentNode, i.toString(), context);
|
|
3113
|
-
if (result) {
|
|
3114
|
-
const { code, functionsToProcess } = result;
|
|
3115
|
-
functionsToProcess?.forEach((value, key) => this._nodeToProcess.set(key, value));
|
|
3116
|
-
return code;
|
|
3117
|
-
}
|
|
3118
|
-
return "";
|
|
3119
|
-
}).flat()]), "}");
|
|
3120
|
-
}
|
|
3121
|
-
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");
|
|
3122
3117
|
}
|
|
3123
|
-
|
|
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) => {
|
|
3124
3124
|
const state = this._states[node.type];
|
|
3125
|
-
if (!state) throw new Error(`[
|
|
3126
|
-
return state(node,
|
|
3127
|
-
}
|
|
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
|
+
};
|
|
3128
3128
|
};
|
|
3129
3129
|
//#endregion
|
|
3130
3130
|
//#region ../packages/compiler/src/compile.ts
|
|
@@ -3141,11 +3141,11 @@ var TypeChecker = class {
|
|
|
3141
3141
|
* into the generated `adoptedStyleSheets` assignment.
|
|
3142
3142
|
* @returns A string containing the compiled Javascript render method body.
|
|
3143
3143
|
*/
|
|
3144
|
-
function compile(input,
|
|
3144
|
+
function compile(input, cssVariableName) {
|
|
3145
3145
|
const nodes = new Parser(new Lexer(input).tokenize()).parse();
|
|
3146
3146
|
return {
|
|
3147
3147
|
javascript: new Generator(nodes).generate(cssVariableName),
|
|
3148
|
-
typescript: new TypeChecker(nodes).generate(
|
|
3148
|
+
typescript: new TypeChecker(nodes).generate()
|
|
3149
3149
|
};
|
|
3150
3150
|
}
|
|
3151
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
|
}
|