release: WRNexusJS 0.8.0
Quality / quality (ubuntu-latest) (push) Failing after 12m19s
Quality / quality (windows-latest) (push) Canceled after 0s

This commit is contained in:
2026-08-04 12:19:09 +05:30
parent 4cebacadfe
commit 72e4d3eceb
108 changed files with 2276 additions and 532 deletions
+59 -9
View File
@@ -39,9 +39,24 @@ function runtimeType(type) {
const value = String(type || "")
.trim()
.replace(/^readonly\s+/, "");
if (/^(?:string|String)(?:\s*\|\s*(?:null|undefined))*$/.test(value)) return "string";
if (/^(?:number|Number)(?:\s*\|\s*(?:null|undefined))*$/.test(value)) return "number";
if (/^(?:boolean|Boolean)(?:\s*\|\s*(?:null|undefined))*$/.test(value)) return "boolean";
const unionParts = value.split("|").map((part) => part.trim());
const concreteParts = unionParts.filter((part) => !/^(?:null|undefined)$/.test(part));
if (
/^(?:string|String)(?:\s*\|\s*(?:null|undefined))*$/.test(value) ||
(concreteParts.length > 0 && concreteParts.every((part) => /^(?:"[^"]*"|'[^']*')$/.test(part)))
)
return "string";
if (
/^(?:number|Number)(?:\s*\|\s*(?:null|undefined))*$/.test(value) ||
(concreteParts.length > 0 &&
concreteParts.every((part) => /^-?(?:\d+\.?\d*|\.\d+)$/.test(part)))
)
return "number";
if (
/^(?:boolean|Boolean)(?:\s*\|\s*(?:null|undefined))*$/.test(value) ||
(concreteParts.length > 0 && concreteParts.every((part) => /^(?:true|false)$/.test(part)))
)
return "boolean";
if (/^bigint(?:\s*\|\s*(?:null|undefined))*$/.test(value)) return "bigint";
if (/^(?:Array\s*<|ReadonlyArray\s*<|.+\[\])/.test(value) || /^\[/.test(value)) return "array";
if (/^(?:Record\s*<|object\b|\{)/.test(value)) return "object";
@@ -138,15 +153,47 @@ function unwrapAttributeValue(value) {
return trimmed;
}
function isWrappedExpression(value) {
const trimmed = value.trim();
return trimmed.startsWith("{") && trimmed.endsWith("}");
}
function expressionLiteralType(value) {
const expression = value.trim();
if (/^(?:true|false)$/.test(expression)) return "boolean";
if (/^-?(?:\d+\.?\d*|\.\d+)$/.test(expression)) return "number";
if (/^(?:"[\s\S]*"|'[\s\S]*'|`[\s\S]*`)$/.test(expression)) return "string";
if (expression.startsWith("[")) return "array";
if (expression.startsWith("{")) return "object";
if (expression === "null") return "null";
return null;
}
function attributeValueType(value, symbols = new Map()) {
const expression = isWrappedExpression(value);
const unwrapped = unwrapAttributeValue(value);
if (/^[A-Za-z_$][\w$]*$/.test(unwrapped) && symbols.has(unwrapped)) {
return symbols.get(unwrapped);
}
if (/^(?:true|false)$/.test(unwrapped)) return "boolean";
if (/^-?(?:\d+\.?\d*|\.\d+)$/.test(unwrapped)) return "number";
if (unwrapped.startsWith("[")) return "array";
if (unwrapped.startsWith("{")) return "object";
const literal = expressionLiteralType(unwrapped);
if (literal) return literal;
if (expression) {
if (
/^!\s*/.test(unwrapped) ||
/(?:===|!==|==|!=|<=|>=|<|>|\bin\b|\binstanceof\b)/.test(unwrapped)
) {
return "boolean";
}
// Dynamic member access, calls, logical expressions and ternaries cannot be
// proven from component metadata alone. Treat them as unknown instead of as
// quoted strings so valid expressions such as `{item.external || false}` do
// not produce false boolean/string diagnostics.
return "unknown";
}
return "string";
}
@@ -154,6 +201,7 @@ function isTypeCompatible(prop, value, symbols = new Map()) {
const expected = runtimeType(prop.type);
if (expected === "unknown" || expected === "null") return true;
const actual = runtimeType(attributeValueType(value, symbols));
if (actual === "unknown") return true;
if (expected === actual) return true;
if (expected === "number" && actual === "string") return Number.isFinite(Number(value));
if (expected === "boolean" && actual === "string")
@@ -255,8 +303,10 @@ function validateComponentTags(source, components) {
end: attribute.nameEnd,
});
}
const literal = stringLiteral(attribute.value) ?? attribute.value;
if (prop.options.length > 0 && !prop.options.includes(literal)) {
const optionValue = isWrappedExpression(attribute.value)
? stringLiteral(unwrapAttributeValue(attribute.value))
: (stringLiteral(attribute.value) ?? attribute.value);
if (optionValue !== null && prop.options.length > 0 && !prop.options.includes(optionValue)) {
diagnostics.push({
severity: "warning",
code: "wrn-component-prop-option",