Migrate expressions, workflow-parser, and languageservice packages to use proper ESM imports with .js extensions that work with node16 moduleResolution. Changes: - Update tsconfig.build.json in each package to use module: node16 and moduleResolution: node16 - Add .js extensions to all relative import paths (Option B approach) - Fix yaml internal type imports in workflow-parser by defining local types - Add skipLibCheck to handle @types/node compatibility issues - Add TypeScript 5.8.3 override in root package.json - Add ESM migration plan documentation The languageserver package is deferred due to test hang issues that need further investigation. Related #154 - Upgrade moduleResolution from node to node16 or nodenext Related #110 - Published ESM code has imports without file extensions Related #64 - expressions: ERR_MODULE_NOT_FOUND attempting to run example Related #146 - Can not import @actions/workflow-parser Test results: - expressions: 1068 tests passed - workflow-parser: 292 tests passed - languageservice: 452 tests passed * docs: update ESM migration plan with findings - Update languageserver blocker: vscode-languageserver v8.0.2 lacks ESM exports (not a test hang issue) - Document that Option B (manual .js extensions) was chosen over Option A due to ts-jest compatibility issues - Add workaround for yaml package internal types (LinePos, NodeBase) - Update migration status table with accurate reason for deferral - Add skipLibCheck note for @types/node compatibility
230 lines
5.4 KiB
TypeScript
230 lines
5.4 KiB
TypeScript
import * as data from "./data/index.js";
|
||
|
||
export function falsy(d: data.ExpressionData): boolean {
|
||
switch (d.kind) {
|
||
case data.Kind.Null:
|
||
return true;
|
||
|
||
case data.Kind.Boolean:
|
||
return !d.value;
|
||
|
||
case data.Kind.Number: {
|
||
const dv = d.value;
|
||
return dv === 0 || isNaN(dv);
|
||
}
|
||
case data.Kind.String:
|
||
return d.value.length === 0;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
export function truthy(d: data.ExpressionData): boolean {
|
||
return !falsy(d);
|
||
}
|
||
|
||
// Similar to the Javascript abstract equality comparison algorithm http://www.ecma-international.org/ecma-262/5.1/#sec-11.9.3.
|
||
// Except objects are not coerced to primitives.
|
||
export function coerceTypes(
|
||
li: data.ExpressionData,
|
||
ri: data.ExpressionData
|
||
): [data.ExpressionData, data.ExpressionData] {
|
||
let lv = li;
|
||
let rv = ri;
|
||
|
||
// Do nothing, same kind
|
||
if (li.kind === ri.kind) {
|
||
return [lv, rv];
|
||
}
|
||
|
||
switch (li.kind) {
|
||
// Number, String
|
||
case data.Kind.Number:
|
||
if (ri.kind === data.Kind.String) {
|
||
rv = new data.NumberData(ri.number());
|
||
return [lv, rv];
|
||
}
|
||
|
||
break;
|
||
|
||
// String, Number
|
||
case data.Kind.String:
|
||
if (ri.kind === data.Kind.Number) {
|
||
lv = new data.NumberData(li.number());
|
||
return [lv, rv];
|
||
}
|
||
|
||
break;
|
||
|
||
// Boolean|Null, Any
|
||
case data.Kind.Null:
|
||
case data.Kind.Boolean:
|
||
lv = new data.NumberData(li.number());
|
||
return coerceTypes(lv, rv);
|
||
}
|
||
|
||
// Any, Boolean|Null
|
||
switch (ri.kind) {
|
||
case data.Kind.Null:
|
||
case data.Kind.Boolean:
|
||
rv = new data.NumberData(ri.number());
|
||
return coerceTypes(lv, rv);
|
||
}
|
||
|
||
return [lv, rv];
|
||
}
|
||
|
||
// Similar to the Javascript abstract equality comparison algorithm http://www.ecma-international.org/ecma-262/5.1/#sec-11.9.3.
|
||
// Except string comparison is OrdinalIgnoreCase, and objects are not coerced to primitives.
|
||
export function equals(lhs: data.ExpressionData, rhs: data.ExpressionData): boolean {
|
||
const [lv, rv] = coerceTypes(lhs, rhs);
|
||
|
||
if (lv.kind != rv.kind) {
|
||
return false;
|
||
}
|
||
|
||
switch (lv.kind) {
|
||
// Null, Null
|
||
case data.Kind.Null:
|
||
return true;
|
||
|
||
// Number, Number
|
||
case data.Kind.Number: {
|
||
const ld = lv.value;
|
||
const rd = (rv as data.NumberData).value;
|
||
if (isNaN(ld) || isNaN(rd)) {
|
||
return false;
|
||
}
|
||
|
||
return ld == rd;
|
||
}
|
||
|
||
// String, String
|
||
case data.Kind.String: {
|
||
const ls = lv.value;
|
||
const rs = (rv as data.StringData).value;
|
||
return toUpperSpecial(ls) === toUpperSpecial(rs);
|
||
}
|
||
|
||
// Boolean, Boolean
|
||
case data.Kind.Boolean: {
|
||
const lb = lv.value;
|
||
const rb = (rv as data.BooleanData).value;
|
||
return lb == rb;
|
||
}
|
||
|
||
// Object, Object
|
||
case data.Kind.Dictionary:
|
||
case data.Kind.Array:
|
||
// Check reference equality
|
||
return lv === rv;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
// Similar to the Javascript abstract equality comparison algorithm http://www.ecma-international.org/ecma-262/5.1/#sec-11.9.3.
|
||
// Except string comparison is OrdinalIgnoreCase, and objects are not coerced to primitives.
|
||
export function greaterThan(lhs: data.ExpressionData, rhs: data.ExpressionData): boolean {
|
||
const [lv, rv] = coerceTypes(lhs, rhs);
|
||
|
||
if (lv.kind != rv.kind) {
|
||
return false;
|
||
}
|
||
|
||
switch (lv.kind) {
|
||
// Number, Number
|
||
case data.Kind.Number: {
|
||
const lf = lv.value;
|
||
const rf = (rv as data.NumberData).value;
|
||
if (isNaN(lf) || isNaN(rf)) {
|
||
return false;
|
||
}
|
||
|
||
return lf > rf;
|
||
}
|
||
|
||
// String, String
|
||
case data.Kind.String: {
|
||
let ls = lv.value;
|
||
let rs = (rv as data.StringData).value;
|
||
ls = toUpperSpecial(ls);
|
||
rs = toUpperSpecial(rs);
|
||
return ls > rs;
|
||
}
|
||
|
||
// Boolean, Boolean
|
||
case data.Kind.Boolean: {
|
||
const lb = lv.value;
|
||
const rb = (rv as data.BooleanData).value;
|
||
return lb && !rb;
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
// Similar to the Javascript abstract equality comparison algorithm http://www.ecma-international.org/ecma-262/5.1/#sec-11.9.3.
|
||
// Except string comparison is OrdinalIgnoreCase, and objects are not coerced to primitives.
|
||
export function lessThan(lhs: data.ExpressionData, rhs: data.ExpressionData): boolean {
|
||
const [lv, rv] = coerceTypes(lhs, rhs);
|
||
|
||
if (lv.kind != rv.kind) {
|
||
return false;
|
||
}
|
||
|
||
switch (lv.kind) {
|
||
// Number, Number
|
||
case data.Kind.Number: {
|
||
const lf = lv.value;
|
||
const rf = (rv as data.NumberData).value;
|
||
if (isNaN(lf) || isNaN(rf)) {
|
||
return false;
|
||
}
|
||
|
||
return lf < rf;
|
||
}
|
||
|
||
// String, String
|
||
case data.Kind.String: {
|
||
let ls = lv.value;
|
||
let rs = (rv as data.StringData).value;
|
||
ls = toUpperSpecial(ls);
|
||
rs = toUpperSpecial(rs);
|
||
return ls < rs;
|
||
}
|
||
|
||
// Boolean, Boolean
|
||
case data.Kind.Boolean: {
|
||
const lb = lv.value;
|
||
const rb = (rv as data.BooleanData).value;
|
||
return !lb && rb;
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
// Do not toUpper the small-dotless-ı
|
||
export function toUpperSpecial(s: string): string {
|
||
const sb: string[] = [];
|
||
let i = 0;
|
||
const len = s.length;
|
||
let found = s.indexOf("ı");
|
||
while (i < len) {
|
||
if (i < found) {
|
||
sb.push(s.substring(i, found).toUpperCase()); // Append upper segment
|
||
i = found;
|
||
} else if (i == found) {
|
||
sb.push(s.substring(i, i + 1));
|
||
i += 1;
|
||
found = s.indexOf("ı", i);
|
||
} else {
|
||
sb.push(s.substring(i).toUpperCase()); // Append upper remaining
|
||
break;
|
||
}
|
||
}
|
||
|
||
return sb.join("");
|
||
}
|