{"record":{"id":"0499f38ec8f8f16d","repo":"can1357/oh-my-pi","slug":"malformed-number-literal-raw","errorCode":null,"errorMessage":"Malformed number literal '${raw}'","messagePattern":"Malformed number literal '(.+?)'","errorType":"exception","errorClass":"OmpTypeError","httpStatus":null,"severity":"error","filePath":"packages/omptype/src/ir.ts","lineNumber":248,"sourceCode":"\t\t\tconst flags = src.slice(j + 1, end);\n\t\t\ttry {\n\t\t\t\ttoks.push({ t: \"regex\", v: new RegExp(source, flags) });\n\t\t\t} catch {\n\t\t\t\tthrow new OmpTypeError(`invalid regular expression \"${src.slice(i, end)}\"`);\n\t\t\t}\n\t\t\ti = end;\n\t\t\tcontinue;\n\t\t}\n\t\tif ((c >= \"0\" && c <= \"9\") || (c === \"-\" && i + 1 < n && src[i + 1] >= \"0\" && src[i + 1] <= \"9\")) {\n\t\t\tlet j = i + 1;\n\t\t\twhile (j < n && /[\\w.+-]/.test(src[j])) j++;\n\t\t\tconst raw = src.slice(i, j);\n\t\t\tif (/^-?(?:0|[1-9]\\d*)n$/.test(raw) && raw !== \"-0n\") {\n\t\t\t\ttoks.push({ t: \"bigint\", v: BigInt(raw.slice(0, -1)) });\n\t\t\t} else {\n\t\t\t\tconst valid =\n\t\t\t\t\t/^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$/.test(raw) && !Object.is(Number(raw), -0) && String(Number(raw)) === raw;\n\t\t\t\tif (!valid) throw new OmpTypeError(`Malformed number literal '${raw}'`);\n\t\t\t\ttoks.push({ t: \"num\", v: Number(raw) });\n\t\t\t}\n\t\t\ti = j;\n\t\t\tcontinue;\n\t\t}\n\t\tif (/[a-zA-Z_$]/.test(c)) {\n\t\t\tlet j = i + 1;\n\t\t\twhile (j < n && /[\\w.$]/.test(src[j])) j++;\n\t\t\ttoks.push({ t: \"id\", v: src.slice(i, j) });\n\t\t\ti = j;\n\t\t\tcontinue;\n\t\t}\n\t\tif (c === \"<\" || c === \">\") {\n\t\t\tif (src[i + 1] === \"=\") {\n\t\t\t\ttoks.push({ t: \"op\", v: `${c}=` });\n\t\t\t\ti += 2;\n\t\t\t} else {\n\t\t\t\ttoks.push({ t: \"op\", v: c });","sourceCodeStart":230,"sourceCodeEnd":266,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/omptype/src/ir.ts#L230-L266","documentation":"The omptype string-definition tokenizer encountered a sequence starting with a digit or '-' that does not form a valid numeric or bigint literal. A number must round-trip exactly: canonical integer or decimal form, no leading zeros, not -0, and String(Number(raw)) === raw. This prevents silently accepting sloppy numeric syntax (e.g. '1.2.3', '01', '1e3', '1_000') that would otherwise parse to an unintended value.","triggerScenarios":"tokenize() (via StrParser's constructor, i.e. any string-definition parse) hits a digit-run containing exponent notation ('1e5'), leading zeros ('007'), double dots ('1.2.3'), trailing junk ('3px'), underscores ('1_000'), or a bigint spelled as '-0n'.","commonSituations":"Porting TypeScript type expressions like 'number >= 1e6' or '0x10' into omptype string definitions; copy-pasted config values with units ('30min'); building definitions programmatically with Number formatting that emits exponents ('1e+21' from String(1e21)).","solutions":["Rewrite the literal in plain canonical decimal form: '1e5' → '100000', '007' → '7', '0x10' → '16'.","Remove unit suffixes or separators from the numeric portion; express units elsewhere in your schema.","If the value comes from code, pre-format it so String(Number(v)) === v (e.g. use plain integer strings), or pass the value as a runtime default (= literal) instead of embedding it.","If a bigint is intended, use the exact canonical form '5n' (never '-0n', which is explicitly rejected)."],"exampleFix":"// before\nconst def = `number > ${min}`; // min = 1e6 -> \"number > 1000000\" ok, but min=1e21 -> \"number > 1e+21\" throws\n// after\nconst def = `number > ${min.toFixed(0)}`; // always plain decimal digits","handlingStrategy":"validation","validationCode":"function isValidNumberLiteral(s) {\n  return /^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?$/.test(s) && !Object.is(Number(s), -0) && String(Number(s)) === s;\n}\nif (!isValidNumberLiteral(String(min))) throw new Error(`bad literal: ${min}`);","typeGuard":null,"tryCatchPattern":"try {\n  const schema = parse(def);\n} catch (e) {\n  if (String(e.message).startsWith(\"Malformed number literal\")) {\n    // fall back to a sanitized literal: String(Number(raw)) or toFixed(0)\n  }\n  throw e;\n}","preventionTips":["Format interpolated numbers with toFixed(0) or String(Number(v)); never let Number.prototype.toString emit exponents into definitions.","Strip units/separators from numbers before embedding them in definition strings.","Avoid exponent, hex, and underscore numeric syntax in string definitions."],"tags":["parser","number-literal","schema-definition"],"backgroundTag":"malformed-number-literal","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}