Compare commits
3 Commits
2a17ca30cb
...
3cecb23ea1
| Author | SHA1 | Date | |
|---|---|---|---|
| 3cecb23ea1 | |||
| 68597c0c24 | |||
| a9d35d03f2 |
@@ -0,0 +1,12 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
+1
-5
@@ -1,8 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "rand",
|
"name": "rand",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"module": "index.ts",
|
|
||||||
"type": "module",
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"rand": "./index.ts"
|
"rand": "./index.ts"
|
||||||
},
|
},
|
||||||
@@ -11,9 +9,7 @@
|
|||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bun": "latest"
|
"@types/bun": "^1",
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"typescript": "^5"
|
"typescript": "^5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,171 @@
|
|||||||
|
import { describe, test, expect } from "bun:test";
|
||||||
|
import { parseArgs, applyPositionals } from "../args";
|
||||||
|
import { defaultOptions, type Options } from "../types";
|
||||||
|
|
||||||
|
describe("parseArgs", () => {
|
||||||
|
test("no args → defaults", () => {
|
||||||
|
const { options, help, version } = parseArgs([]);
|
||||||
|
expect(help).toBe(false);
|
||||||
|
expect(version).toBe(false);
|
||||||
|
expect(options.dist).toBe("uniform");
|
||||||
|
expect(options.count).toBe(1);
|
||||||
|
expect(options.decimals).toBe(0);
|
||||||
|
expect(options.min).toBe(0);
|
||||||
|
expect(options.max).toBe(100);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("-h / --help", () => {
|
||||||
|
expect(parseArgs(["-h"]).help).toBe(true);
|
||||||
|
expect(parseArgs(["--help"]).help).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("-V / --version", () => {
|
||||||
|
expect(parseArgs(["-V"]).version).toBe(true);
|
||||||
|
expect(parseArgs(["--version"]).version).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("-c / --count", () => {
|
||||||
|
expect(parseArgs(["-c", "5"]).options.count).toBe(5);
|
||||||
|
expect(parseArgs(["--count", "10"]).options.count).toBe(10);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("-c with invalid value throws", () => {
|
||||||
|
expect(() => parseArgs(["-c", "0"])).toThrow();
|
||||||
|
expect(() => parseArgs(["-c", "-1"])).toThrow();
|
||||||
|
expect(() => parseArgs(["-c", "1000001"])).toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("-f with no arg defaults to 2", () => {
|
||||||
|
expect(parseArgs(["-f"]).options.decimals).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("-f with space-separated arg", () => {
|
||||||
|
expect(parseArgs(["-f", "3"]).options.decimals).toBe(3);
|
||||||
|
expect(parseArgs(["-f", "0"]).options.decimals).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("-fN combined form", () => {
|
||||||
|
expect(parseArgs(["-f1"]).options.decimals).toBe(1);
|
||||||
|
expect(parseArgs(["-f5"]).options.decimals).toBe(5);
|
||||||
|
expect(parseArgs(["-f0"]).options.decimals).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("--float with no arg defaults to 2", () => {
|
||||||
|
expect(parseArgs(["--float"]).options.decimals).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("--float with arg", () => {
|
||||||
|
expect(parseArgs(["--float", "4"]).options.decimals).toBe(4);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("-d / --dist", () => {
|
||||||
|
expect(parseArgs(["-d", "normal"]).options.dist).toBe("normal");
|
||||||
|
expect(parseArgs(["--dist", "poisson"]).options.dist).toBe("poisson");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("-d with invalid dist throws", () => {
|
||||||
|
expect(() => parseArgs(["-d", "invalid"])).toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("positional args for uniform", () => {
|
||||||
|
const { options } = parseArgs(["10"]);
|
||||||
|
expect(options.max).toBe(10);
|
||||||
|
expect(options.min).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("positional args for uniform with two values", () => {
|
||||||
|
const { options } = parseArgs(["10", "20"]);
|
||||||
|
expect(options.min).toBe(10);
|
||||||
|
expect(options.max).toBe(20);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("positional args for normal", () => {
|
||||||
|
const { options } = parseArgs(["-d", "normal", "100", "15"]);
|
||||||
|
expect(options.mean).toBe(100);
|
||||||
|
expect(options.stddev).toBe(15);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("-- separator passes everything as positional", () => {
|
||||||
|
const { options } = parseArgs(["--", "-10", "-5"]);
|
||||||
|
expect(options.min).toBe(-10);
|
||||||
|
expect(options.max).toBe(-5);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("-- separator stops flag parsing", () => {
|
||||||
|
// numbers after -- become positional
|
||||||
|
const { options } = parseArgs(["--", "5"]);
|
||||||
|
expect(options.max).toBe(5);
|
||||||
|
// non-numeric after -- still throws
|
||||||
|
expect(() => parseArgs(["--", "-h"])).toThrow("not a number");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("unknown flag throws", () => {
|
||||||
|
expect(() => parseArgs(["--unknown"])).toThrow("unknown option");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("applyPositionals", () => {
|
||||||
|
function makeOpts(dist: string, overrides?: Partial<Options>): Options {
|
||||||
|
return { ...defaultOptions(dist as Options["dist"]), dist: dist as Options["dist"], ...overrides };
|
||||||
|
}
|
||||||
|
|
||||||
|
test("uniform: 0 args leaves defaults", () => {
|
||||||
|
const o = makeOpts("uniform");
|
||||||
|
applyPositionals(o, []);
|
||||||
|
expect(o.min).toBe(0);
|
||||||
|
expect(o.max).toBe(100);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("uniform: 1 arg sets max", () => {
|
||||||
|
const o = makeOpts("uniform");
|
||||||
|
applyPositionals(o, ["50"]);
|
||||||
|
expect(o.max).toBe(50);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("uniform: 2 args sets min and max", () => {
|
||||||
|
const o = makeOpts("uniform");
|
||||||
|
applyPositionals(o, ["10", "20"]);
|
||||||
|
expect(o.min).toBe(10);
|
||||||
|
expect(o.max).toBe(20);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("uniform: min > max throws", () => {
|
||||||
|
const o = makeOpts("uniform");
|
||||||
|
expect(() => applyPositionals(o, ["10", "5"])).toThrow("min");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("uniform: too many args throws", () => {
|
||||||
|
const o = makeOpts("uniform");
|
||||||
|
expect(() => applyPositionals(o, ["1", "2", "3"])).toThrow("uniform expects");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("normal: stddev <= 0 throws", () => {
|
||||||
|
const o = makeOpts("normal");
|
||||||
|
expect(() => applyPositionals(o, ["0", "-1"])).toThrow("stddev");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("binomial: prob out of range throws", () => {
|
||||||
|
const o = makeOpts("binomial");
|
||||||
|
expect(() => applyPositionals(o, ["10", "1.5"])).toThrow("prob");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("poisson: lambda <= 0 throws", () => {
|
||||||
|
const o = makeOpts("poisson");
|
||||||
|
expect(() => applyPositionals(o, ["0"])).toThrow("lambda");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("poisson: too many args throws", () => {
|
||||||
|
const o = makeOpts("poisson");
|
||||||
|
expect(() => applyPositionals(o, ["1", "2"])).toThrow("poisson expects");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("exponential: lambda <= 0 throws", () => {
|
||||||
|
const o = makeOpts("exponential");
|
||||||
|
expect(() => applyPositionals(o, ["0"])).toThrow("lambda");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("hypergeometric: K > N throws", () => {
|
||||||
|
const o = makeOpts("hypergeometric");
|
||||||
|
expect(() => applyPositionals(o, ["100", "150", "10"])).toThrow("K");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,214 @@
|
|||||||
|
import { describe, test, expect } from "bun:test";
|
||||||
|
import { generate } from "../dist";
|
||||||
|
import { type Options, type Dist, defaultOptions } from "../types";
|
||||||
|
|
||||||
|
function opts(overrides: Partial<Options> & { dist: Dist }): Options {
|
||||||
|
return { ...defaultOptions(overrides.dist), ...overrides };
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("generate", () => {
|
||||||
|
test("uniform produces values in [min, max] for decimals=0 (inclusive)", () => {
|
||||||
|
const n = 2000;
|
||||||
|
const o = opts({ dist: "uniform", min: 10, max: 20, count: n, decimals: 0 });
|
||||||
|
const values = [...generate(o)];
|
||||||
|
expect(values.length).toBe(n);
|
||||||
|
for (const v of values) {
|
||||||
|
expect(v).toBeGreaterThanOrEqual(10);
|
||||||
|
expect(v).toBeLessThanOrEqual(20);
|
||||||
|
expect(Number.isInteger(v)).toBe(true);
|
||||||
|
}
|
||||||
|
// All values in [10,20] must appear at least once with high probability
|
||||||
|
const seen = new Set(values);
|
||||||
|
for (let i = 10; i <= 20; i++) expect(seen.has(i)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("uniform produces values in [min, max) for decimals>0", () => {
|
||||||
|
const o = opts({ dist: "uniform", min: 0, max: 1, count: 500, decimals: 4 });
|
||||||
|
const values = [...generate(o)];
|
||||||
|
for (const v of values) {
|
||||||
|
expect(v).toBeGreaterThanOrEqual(0);
|
||||||
|
expect(v).toBeLessThan(1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("uniform negative range produces inclusive values", () => {
|
||||||
|
const o = opts({ dist: "uniform", min: -10, max: -5, count: 100, decimals: 0 });
|
||||||
|
const values = [...generate(o)];
|
||||||
|
for (const v of values) {
|
||||||
|
expect(v).toBeGreaterThanOrEqual(-10);
|
||||||
|
expect(v).toBeLessThanOrEqual(-5);
|
||||||
|
expect(Number.isInteger(v)).toBe(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("uniform single-value range", () => {
|
||||||
|
const o = opts({ dist: "uniform", min: 7, max: 7, count: 50, decimals: 0 });
|
||||||
|
const values = [...generate(o)];
|
||||||
|
for (const v of values) expect(v).toBe(7);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("normal produces values around mean", () => {
|
||||||
|
const o = opts({ dist: "normal", mean: 100, stddev: 15, count: 2000 });
|
||||||
|
const values = [...generate(o)];
|
||||||
|
const avg = values.reduce((a, b) => a + b, 0) / values.length;
|
||||||
|
// Mean should be within ~3 std errors of 100
|
||||||
|
expect(avg).toBeGreaterThan(98);
|
||||||
|
expect(avg).toBeLessThan(102);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("normal with decimals=0 produces integers via Math.round", () => {
|
||||||
|
const o = opts({ dist: "normal", mean: 0, stddev: 1, count: 500, decimals: 0 });
|
||||||
|
const values = [...generate(o)];
|
||||||
|
for (const v of values) {
|
||||||
|
expect(Number.isInteger(v)).toBe(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("binomial produces values in [0, n]", () => {
|
||||||
|
const o = opts({ dist: "binomial", trials: 10, prob: 0.5, count: 500 });
|
||||||
|
const values = [...generate(o)];
|
||||||
|
for (const v of values) {
|
||||||
|
expect(v).toBeGreaterThanOrEqual(0);
|
||||||
|
expect(v).toBeLessThanOrEqual(10);
|
||||||
|
expect(Number.isInteger(v)).toBe(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("binomial p=0 always returns 0", () => {
|
||||||
|
const o = opts({ dist: "binomial", trials: 10, prob: 0, count: 50 });
|
||||||
|
for (const v of generate(o)) expect(v).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("binomial p=1 always returns n", () => {
|
||||||
|
const o = opts({ dist: "binomial", trials: 10, prob: 1, count: 50 });
|
||||||
|
for (const v of generate(o)) expect(v).toBe(10);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("binomial trials=0 always returns 0", () => {
|
||||||
|
const o = opts({ dist: "binomial", trials: 0, prob: 0.5, count: 50 });
|
||||||
|
for (const v of generate(o)) expect(v).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("binomial large n uses Normal approximation", () => {
|
||||||
|
const o = opts({ dist: "binomial", trials: 20000, prob: 0.5, count: 200 });
|
||||||
|
const values = [...generate(o)];
|
||||||
|
const avg = values.reduce((a, b) => a + b, 0) / values.length;
|
||||||
|
expect(avg).toBeGreaterThan(9500);
|
||||||
|
expect(avg).toBeLessThan(10500);
|
||||||
|
for (const v of values) {
|
||||||
|
expect(v).toBeGreaterThanOrEqual(0);
|
||||||
|
expect(v).toBeLessThanOrEqual(20000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("poisson produces non-negative integers", () => {
|
||||||
|
const o = opts({ dist: "poisson", lambda: 5, count: 500 });
|
||||||
|
const values = [...generate(o)];
|
||||||
|
for (const v of values) {
|
||||||
|
expect(v).toBeGreaterThanOrEqual(0);
|
||||||
|
expect(Number.isInteger(v)).toBe(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("poisson large lambda uses Normal approximation", () => {
|
||||||
|
const o = opts({ dist: "poisson", lambda: 20000, count: 200 });
|
||||||
|
const values = [...generate(o)];
|
||||||
|
const avg = values.reduce((a, b) => a + b, 0) / values.length;
|
||||||
|
expect(avg).toBeGreaterThan(19000);
|
||||||
|
expect(avg).toBeLessThan(21000);
|
||||||
|
for (const v of values) {
|
||||||
|
expect(v).toBeGreaterThanOrEqual(0);
|
||||||
|
expect(Number.isInteger(v)).toBe(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("exponential produces non-negative values", () => {
|
||||||
|
// decimals=0: Math.round can make very small values become 0
|
||||||
|
const o = opts({ dist: "exponential", lambda: 1, count: 500 });
|
||||||
|
for (const v of generate(o)) {
|
||||||
|
expect(v).toBeGreaterThanOrEqual(0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("exponential mean approximates 1/lambda", () => {
|
||||||
|
const lambda = 2;
|
||||||
|
const o = opts({ dist: "exponential", lambda, count: 2000 });
|
||||||
|
const values = [...generate(o)];
|
||||||
|
const avg = values.reduce((a, b) => a + b, 0) / values.length;
|
||||||
|
expect(avg).toBeGreaterThan(0.35);
|
||||||
|
expect(avg).toBeLessThan(0.65);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("hypergeometric produces values in [0, min(K, n)]", () => {
|
||||||
|
const o = opts({ dist: "hypergeometric", popSize: 100, successes: 30, draws: 10, count: 500 });
|
||||||
|
const values = [...generate(o)];
|
||||||
|
for (const v of values) {
|
||||||
|
expect(v).toBeGreaterThanOrEqual(0);
|
||||||
|
expect(v).toBeLessThanOrEqual(10);
|
||||||
|
expect(Number.isInteger(v)).toBe(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("hypergeometric K=0 always returns 0", () => {
|
||||||
|
const o = opts({ dist: "hypergeometric", popSize: 100, successes: 0, draws: 10, count: 50 });
|
||||||
|
for (const v of generate(o)) expect(v).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("hypergeometric draws=0 always returns 0", () => {
|
||||||
|
const o = opts({ dist: "hypergeometric", popSize: 100, successes: 50, draws: 0, count: 50 });
|
||||||
|
for (const v of generate(o)) expect(v).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("generator yields exactly count values", () => {
|
||||||
|
for (const count of [1, 5, 100]) {
|
||||||
|
const o = opts({ dist: "uniform", count });
|
||||||
|
expect([...generate(o)].length).toBe(count);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// validateOptions defense-in-depth tests
|
||||||
|
test("throws on count < 1", () => {
|
||||||
|
const o = opts({ dist: "uniform", count: 0 });
|
||||||
|
expect(() => [...generate(o)]).toThrow("invalid count");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("throws on negative decimals", () => {
|
||||||
|
const o = opts({ dist: "uniform", decimals: -1 });
|
||||||
|
expect(() => [...generate(o)]).toThrow("decimals must be 0–100");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("throws on decimals > 100", () => {
|
||||||
|
const o = opts({ dist: "uniform", decimals: 101 });
|
||||||
|
expect(() => [...generate(o)]).toThrow("decimals must be 0–100");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("throws on min > max", () => {
|
||||||
|
const o = opts({ dist: "uniform", min: 10, max: 5 });
|
||||||
|
expect(() => [...generate(o)]).toThrow("min");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("throws on stddev <= 0", () => {
|
||||||
|
const o = opts({ dist: "normal", stddev: 0 });
|
||||||
|
expect(() => [...generate(o)]).toThrow("stddev");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("throws on prob out of range", () => {
|
||||||
|
const o = opts({ dist: "binomial", prob: 1.5 });
|
||||||
|
expect(() => [...generate(o)]).toThrow("prob");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("throws on lambda <= 0 for poisson", () => {
|
||||||
|
const o = opts({ dist: "poisson", lambda: 0 });
|
||||||
|
expect(() => [...generate(o)]).toThrow("lambda");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("throws on lambda <= 0 for exponential", () => {
|
||||||
|
const o = opts({ dist: "exponential", lambda: -1 });
|
||||||
|
expect(() => [...generate(o)]).toThrow("lambda");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("throws on hypergeometric K > N", () => {
|
||||||
|
const o = opts({ dist: "hypergeometric", popSize: 100, successes: 150, draws: 10 });
|
||||||
|
expect(() => [...generate(o)]).toThrow("K");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import { describe, test, expect } from "bun:test";
|
||||||
|
import { parseStdinNumbers } from "../stdin";
|
||||||
|
|
||||||
|
describe("parseStdinNumbers", () => {
|
||||||
|
test("empty string returns []", () => {
|
||||||
|
expect(parseStdinNumbers("")).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("whitespace-only returns []", () => {
|
||||||
|
expect(parseStdinNumbers(" \n\t ")).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("single number", () => {
|
||||||
|
expect(parseStdinNumbers("42")).toEqual([42]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("multiple numbers", () => {
|
||||||
|
expect(parseStdinNumbers("5 2")).toEqual([5, 2]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("numbers separated by newlines", () => {
|
||||||
|
expect(parseStdinNumbers("5\n2\n3")).toEqual([5, 2, 3]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("numbers separated by tabs and multiple spaces", () => {
|
||||||
|
expect(parseStdinNumbers("5\t2 3")).toEqual([5, 2, 3]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("leading and trailing whitespace is trimmed", () => {
|
||||||
|
expect(parseStdinNumbers(" 10 20 \n")).toEqual([10, 20]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("negative numbers", () => {
|
||||||
|
expect(parseStdinNumbers("-1 -0.5")).toEqual([-1, -0.5]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("float numbers", () => {
|
||||||
|
expect(parseStdinNumbers("3.14 2.718")).toEqual([3.14, 2.718]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("non-numeric input returns []", () => {
|
||||||
|
expect(parseStdinNumbers("abc")).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("mixed numeric and non-numeric returns []", () => {
|
||||||
|
expect(parseStdinNumbers("5 abc 10")).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("empty string after trim returns []", () => {
|
||||||
|
expect(parseStdinNumbers(" ")).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
+28
-29
@@ -1,17 +1,17 @@
|
|||||||
import { type Options, type Dist, DISTS, defaultOptions } from "./types";
|
import { type Options, type Dist, DISTS, defaultOptions } from "./types";
|
||||||
import { showHelp } from "./help";
|
import { validateOptions } from "./dist";
|
||||||
|
|
||||||
export function showError(msg: string): never {
|
export function showError(msg: string): never {
|
||||||
console.error(`rand: ${msg}\nTry 'rand --help' for usage.`);
|
throw new Error(`${msg}\nTry 'rand --help' for usage.`);
|
||||||
process.exit(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseArgs(raw: string[]): { options: Options; help: boolean } {
|
export function parseArgs(raw: string[]): { options: Options; help: boolean; version: boolean } {
|
||||||
const positional: string[] = [];
|
const positional: string[] = [];
|
||||||
let count = 1;
|
let count = 1;
|
||||||
let decimals = 0;
|
let decimals = 0;
|
||||||
let dist: Dist = "uniform";
|
let dist: Dist = "uniform";
|
||||||
let help = false;
|
let help = false;
|
||||||
|
let version = false;
|
||||||
|
|
||||||
let i = 0;
|
let i = 0;
|
||||||
while (i < raw.length) {
|
while (i < raw.length) {
|
||||||
@@ -23,10 +23,21 @@ export function parseArgs(raw: string[]): { options: Options; help: boolean } {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (arg === "-V" || arg === "--version") {
|
||||||
|
version = true;
|
||||||
|
i++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// -f (plain) → default 2 decimal places
|
// -f (plain) → default 2 decimal places
|
||||||
if (arg === "-f") {
|
if (arg === "-f") {
|
||||||
decimals = 2;
|
|
||||||
i++;
|
i++;
|
||||||
|
if (i < raw.length && /^\d+$/.test(raw[i]!)) {
|
||||||
|
decimals = parseInt(raw[i]!, 10);
|
||||||
|
i++;
|
||||||
|
} else {
|
||||||
|
decimals = 2;
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,21 +65,21 @@ export function parseArgs(raw: string[]): { options: Options; help: boolean } {
|
|||||||
i++;
|
i++;
|
||||||
if (i >= raw.length) showError("--dist requires a distribution name");
|
if (i >= raw.length) showError("--dist requires a distribution name");
|
||||||
const name = raw[i]!;
|
const name = raw[i]!;
|
||||||
if (!(DISTS as string[]).includes(name)) {
|
if (!DISTS.includes(name as Dist)) {
|
||||||
showError(`unknown distribution: ${name}\n valid: ${DISTS.join(", ")}`);
|
showError(`unknown distribution: ${name}\n valid: ${DISTS.join(", ")}`);
|
||||||
}
|
}
|
||||||
dist = name as Dist;
|
dist = name as Dist;
|
||||||
i++;
|
i++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -c, --count <n>
|
// -c, --count <n>
|
||||||
if (arg === "-c" || arg === "--count") {
|
if (arg === "-c" || arg === "--count") {
|
||||||
i++;
|
i++;
|
||||||
if (i >= raw.length) showError("--count requires a value");
|
if (i >= raw.length) showError("--count requires a value");
|
||||||
const n = parseInt(raw[i]!, 10);
|
const c = parseInt(raw[i]!, 10);
|
||||||
if (isNaN(n) || n < 1) showError(`invalid count: ${raw[i]}`);
|
if (isNaN(c) || c < 1) showError(`invalid count: ${raw[i]}`);
|
||||||
count = n;
|
if (c > 1_000_000) showError(`count must be <= 1,000,000, got ${c}`);
|
||||||
|
count = c;
|
||||||
i++;
|
i++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -103,8 +114,7 @@ export function parseArgs(raw: string[]): { options: Options; help: boolean } {
|
|||||||
opts.count = count;
|
opts.count = count;
|
||||||
opts.decimals = decimals;
|
opts.decimals = decimals;
|
||||||
applyPositionals(opts, positional);
|
applyPositionals(opts, positional);
|
||||||
|
return { options: opts, help, version };
|
||||||
return { options: opts, help };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function applyPositionals(opts: Options, args: string[]): void {
|
export function applyPositionals(opts: Options, args: string[]): void {
|
||||||
@@ -121,7 +131,6 @@ export function applyPositionals(opts: Options, args: string[]): void {
|
|||||||
} else {
|
} else {
|
||||||
showError(`uniform expects 0–2 args, got ${nums.length}: ${args.join(" ")}`);
|
showError(`uniform expects 0–2 args, got ${nums.length}: ${args.join(" ")}`);
|
||||||
}
|
}
|
||||||
if (opts.min > opts.max) showError(`min (${opts.min}) > max (${opts.max})`);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "normal": {
|
case "normal": {
|
||||||
@@ -134,7 +143,6 @@ export function applyPositionals(opts: Options, args: string[]): void {
|
|||||||
} else {
|
} else {
|
||||||
showError(`normal expects 0–2 args, got ${nums.length}: ${args.join(" ")}`);
|
showError(`normal expects 0–2 args, got ${nums.length}: ${args.join(" ")}`);
|
||||||
}
|
}
|
||||||
if (opts.stddev <= 0) showError(`stddev must be > 0, got ${opts.stddev}`);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "binomial": {
|
case "binomial": {
|
||||||
@@ -147,10 +155,6 @@ export function applyPositionals(opts: Options, args: string[]): void {
|
|||||||
} else {
|
} else {
|
||||||
showError(`binomial expects 0–2 args, got ${nums.length}: ${args.join(" ")}`);
|
showError(`binomial expects 0–2 args, got ${nums.length}: ${args.join(" ")}`);
|
||||||
}
|
}
|
||||||
if (opts.trials < 0 || !Number.isInteger(opts.trials)) {
|
|
||||||
showError(`trials must be a non-negative integer, got ${opts.trials}`);
|
|
||||||
}
|
|
||||||
if (opts.prob < 0 || opts.prob > 1) showError(`prob must be 0–1, got ${opts.prob}`);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "poisson": {
|
case "poisson": {
|
||||||
@@ -160,7 +164,6 @@ export function applyPositionals(opts: Options, args: string[]): void {
|
|||||||
} else {
|
} else {
|
||||||
showError(`poisson expects 0–1 arg, got ${nums.length}: ${args.join(" ")}`);
|
showError(`poisson expects 0–1 arg, got ${nums.length}: ${args.join(" ")}`);
|
||||||
}
|
}
|
||||||
if (opts.lambda <= 0) showError(`lambda must be > 0, got ${opts.lambda}`);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "exponential": {
|
case "exponential": {
|
||||||
@@ -170,7 +173,6 @@ export function applyPositionals(opts: Options, args: string[]): void {
|
|||||||
} else {
|
} else {
|
||||||
showError(`exponential expects 0–1 arg, got ${nums.length}: ${args.join(" ")}`);
|
showError(`exponential expects 0–1 arg, got ${nums.length}: ${args.join(" ")}`);
|
||||||
}
|
}
|
||||||
if (opts.lambda <= 0) showError(`lambda must be > 0, got ${opts.lambda}`);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "hypergeometric": {
|
case "hypergeometric": {
|
||||||
@@ -181,18 +183,15 @@ export function applyPositionals(opts: Options, args: string[]): void {
|
|||||||
if (nums.length > 3) {
|
if (nums.length > 3) {
|
||||||
showError(`hypergeometric expects 0–3 args, got ${nums.length}: ${args.join(" ")}`);
|
showError(`hypergeometric expects 0–3 args, got ${nums.length}: ${args.join(" ")}`);
|
||||||
}
|
}
|
||||||
if (opts.popSize < 0 || !Number.isInteger(opts.popSize)) {
|
|
||||||
showError(`population size N must be a non-negative integer, got ${opts.popSize}`);
|
|
||||||
}
|
|
||||||
if (opts.successes < 0 || opts.successes > opts.popSize || !Number.isInteger(opts.successes)) {
|
|
||||||
showError(`successes K must be 0–N, got ${opts.successes} (N=${opts.popSize})`);
|
|
||||||
}
|
|
||||||
if (opts.draws < 0 || opts.draws > opts.popSize || !Number.isInteger(opts.draws)) {
|
|
||||||
showError(`draws n must be 0–N, got ${opts.draws} (N=${opts.popSize})`);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
validateOptions(opts);
|
||||||
|
} catch (e) {
|
||||||
|
showError((e as Error).message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseNum(s: string): number {
|
function parseNum(s: string): number {
|
||||||
|
|||||||
+77
-20
@@ -4,16 +4,34 @@ import { type Options } from "./types";
|
|||||||
// Individual distribution samplers
|
// Individual distribution samplers
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
/** Box-Muller transform. Each call consumes 2 uniform randoms. */
|
// Cached second normal deviate from Box-Muller transform.
|
||||||
|
let spareNormal: number | null = null;
|
||||||
|
|
||||||
|
/** Box-Muller with pair caching: uses half the RNG calls of naive Box-Muller. */
|
||||||
function normalRandom(mean: number, stddev: number): number {
|
function normalRandom(mean: number, stddev: number): number {
|
||||||
|
if (spareNormal !== null) {
|
||||||
|
const v = spareNormal;
|
||||||
|
spareNormal = null;
|
||||||
|
return mean + stddev * v;
|
||||||
|
}
|
||||||
let u1 = Math.random();
|
let u1 = Math.random();
|
||||||
while (u1 === 0) u1 = Math.random(); // avoid log(0)
|
for (let attempt = 0; u1 === 0 && attempt < 100; attempt++) {
|
||||||
|
u1 = Math.random();
|
||||||
|
}
|
||||||
|
if (u1 === 0) u1 = Number.EPSILON;
|
||||||
const u2 = Math.random();
|
const u2 = Math.random();
|
||||||
return mean + stddev * Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
|
const r = Math.sqrt(-2 * Math.log(u1));
|
||||||
|
spareNormal = r * Math.sin(2 * Math.PI * u2);
|
||||||
|
return mean + stddev * r * Math.cos(2 * Math.PI * u2);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Sum of Bernoulli trials. */
|
/** Bernoulli trials; Normal approximation when n>10_000 and np, n(1-p) both >5. */
|
||||||
function binomialRandom(n: number, p: number): number {
|
function binomialRandom(n: number, p: number): number {
|
||||||
|
if (n > 10_000 && n * p > 5 && n * (1 - p) > 5) {
|
||||||
|
const mean = n * p;
|
||||||
|
const stddev = Math.sqrt(n * p * (1 - p));
|
||||||
|
return Math.max(0, Math.min(n, Math.round(normalRandom(mean, stddev))));
|
||||||
|
}
|
||||||
let s = 0;
|
let s = 0;
|
||||||
for (let i = 0; i < n; i++) {
|
for (let i = 0; i < n; i++) {
|
||||||
if (Math.random() < p) s++;
|
if (Math.random() < p) s++;
|
||||||
@@ -21,8 +39,11 @@ function binomialRandom(n: number, p: number): number {
|
|||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Knuth's algorithm. */
|
/** Knuth's algorithm; Normal approximation for λ > 100 (avoids exp underflow). */
|
||||||
function poissonRandom(lambda: number): number {
|
function poissonRandom(lambda: number): number {
|
||||||
|
if (lambda > 100) {
|
||||||
|
return Math.max(0, Math.round(normalRandom(lambda, Math.sqrt(lambda))));
|
||||||
|
}
|
||||||
const L = Math.exp(-lambda);
|
const L = Math.exp(-lambda);
|
||||||
let k = 0;
|
let k = 0;
|
||||||
let p = 1;
|
let p = 1;
|
||||||
@@ -33,9 +54,9 @@ function poissonRandom(lambda: number): number {
|
|||||||
return k - 1;
|
return k - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Inverse CDF. */
|
/** Inverse CDF. Caller must ensure λ > 0. */
|
||||||
function exponentialRandom(lambda: number): number {
|
function exponentialRandom(lambda: number): number {
|
||||||
return -Math.log(Math.random()) / lambda;
|
return -Math.log(Math.random() || Number.EPSILON) / lambda;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Urn model — simulate drawing without replacement. */
|
/** Urn model — simulate drawing without replacement. */
|
||||||
@@ -58,31 +79,67 @@ function hypergeometricRandom(N: number, K: number, n: number): number {
|
|||||||
// Dispatcher
|
// Dispatcher
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
export function generate(opts: Options): number[] {
|
export function validateOptions(opts: Options): void {
|
||||||
const results: number[] = [];
|
if (!Number.isInteger(opts.count) || opts.count < 1) {
|
||||||
for (let i = 0; i < opts.count; i++) {
|
throw new Error(`invalid count: ${opts.count}`);
|
||||||
|
}
|
||||||
|
if (!Number.isInteger(opts.decimals) || opts.decimals < 0 || opts.decimals > 100) {
|
||||||
|
throw new Error(`decimals must be 0–100, got ${opts.decimals}`);
|
||||||
|
}
|
||||||
switch (opts.dist) {
|
switch (opts.dist) {
|
||||||
case "uniform":
|
case "uniform":
|
||||||
results.push(Math.random() * (opts.max - opts.min) + opts.min);
|
if (opts.min > opts.max) throw new Error(`min (${opts.min}) > max (${opts.max})`);
|
||||||
break;
|
break;
|
||||||
case "normal":
|
case "normal":
|
||||||
results.push(normalRandom(opts.mean, opts.stddev));
|
if (opts.stddev <= 0) throw new Error(`stddev must be > 0, got ${opts.stddev}`);
|
||||||
break;
|
break;
|
||||||
case "binomial":
|
case "binomial":
|
||||||
results.push(binomialRandom(opts.trials, opts.prob));
|
if (opts.trials < 0 || !Number.isInteger(opts.trials))
|
||||||
|
throw new Error(`trials must be a non-negative integer, got ${opts.trials}`);
|
||||||
|
if (opts.prob < 0 || opts.prob > 1)
|
||||||
|
throw new Error(`prob must be 0–1, got ${opts.prob}`);
|
||||||
break;
|
break;
|
||||||
case "poisson":
|
case "poisson":
|
||||||
results.push(poissonRandom(opts.lambda));
|
|
||||||
break;
|
|
||||||
case "exponential":
|
case "exponential":
|
||||||
results.push(exponentialRandom(opts.lambda));
|
if (opts.lambda <= 0) throw new Error(`lambda must be > 0, got ${opts.lambda}`);
|
||||||
break;
|
break;
|
||||||
case "hypergeometric":
|
case "hypergeometric":
|
||||||
results.push(
|
if (opts.popSize < 0 || !Number.isInteger(opts.popSize))
|
||||||
hypergeometricRandom(opts.popSize, opts.successes, opts.draws),
|
throw new Error(`population size N must be a non-negative integer, got ${opts.popSize}`);
|
||||||
);
|
if (opts.successes < 0 || opts.successes > opts.popSize || !Number.isInteger(opts.successes))
|
||||||
|
throw new Error(`successes K must be 0–N, got ${opts.successes} (N=${opts.popSize})`);
|
||||||
|
if (opts.draws < 0 || opts.draws > opts.popSize || !Number.isInteger(opts.draws))
|
||||||
|
throw new Error(`draws n must be 0–N, got ${opts.draws} (N=${opts.popSize})`);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return results;
|
|
||||||
|
export function* generate(opts: Options): Generator<number> {
|
||||||
|
validateOptions(opts);
|
||||||
|
for (let i = 0; i < opts.count; i++) {
|
||||||
|
let v: number;
|
||||||
|
switch (opts.dist) {
|
||||||
|
case "uniform":
|
||||||
|
v = opts.decimals === 0
|
||||||
|
? Math.floor(Math.random() * (opts.max - opts.min + 1)) + opts.min
|
||||||
|
: Math.random() * (opts.max - opts.min) + opts.min;
|
||||||
|
break;
|
||||||
|
case "normal":
|
||||||
|
v = normalRandom(opts.mean, opts.stddev);
|
||||||
|
break;
|
||||||
|
case "binomial":
|
||||||
|
v = binomialRandom(opts.trials, opts.prob);
|
||||||
|
break;
|
||||||
|
case "poisson":
|
||||||
|
v = poissonRandom(opts.lambda);
|
||||||
|
break;
|
||||||
|
case "exponential":
|
||||||
|
v = exponentialRandom(opts.lambda);
|
||||||
|
break;
|
||||||
|
case "hypergeometric":
|
||||||
|
v = hypergeometricRandom(opts.popSize, opts.successes, opts.draws);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
yield opts.decimals > 0 ? v : Math.round(v);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-1
@@ -1,3 +1,5 @@
|
|||||||
|
import { VERSION } from "./types";
|
||||||
|
|
||||||
export const HELP = `rand — Generate random numbers
|
export const HELP = `rand — Generate random numbers
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
@@ -5,10 +7,11 @@ Usage:
|
|||||||
|
|
||||||
Options:
|
Options:
|
||||||
-c, --count <n> Number of random numbers to output (default: 1)
|
-c, --count <n> Number of random numbers to output (default: 1)
|
||||||
-f[N] Decimal places: -f = -f2 (default: 2)
|
-f[N] Decimal places: -f = -f2, -f3 = 3 places
|
||||||
--float [N] Long form of -f
|
--float [N] Long form of -f
|
||||||
-d, --dist <name> Distribution (default: uniform)
|
-d, --dist <name> Distribution (default: uniform)
|
||||||
-h, --help Show this help
|
-h, --help Show this help
|
||||||
|
-V, --version Show version
|
||||||
|
|
||||||
Distributions and their positional args:
|
Distributions and their positional args:
|
||||||
uniform rand [min] [max] (default: 0 100)
|
uniform rand [min] [max] (default: 0 100)
|
||||||
@@ -33,3 +36,8 @@ export function showHelp(): never {
|
|||||||
console.log(HELP);
|
console.log(HELP);
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function showVersion(): never {
|
||||||
|
console.log(`rand v${VERSION}`);
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|||||||
+9
-8
@@ -1,12 +1,13 @@
|
|||||||
import { parseArgs, applyPositionals } from "./args";
|
import { parseArgs, applyPositionals } from "./args";
|
||||||
import { showHelp } from "./help";
|
import { showHelp, showVersion } from "./help";
|
||||||
import { readStdin, parseStdinNumbers } from "./stdin";
|
import { readStdin, parseStdinNumbers } from "./stdin";
|
||||||
import { generate } from "./dist";
|
import { generate } from "./dist";
|
||||||
|
|
||||||
export async function main(): Promise<void> {
|
async function main(): Promise<void> {
|
||||||
const argv = Bun.argv.slice(2);
|
const argv = Bun.argv.slice(2);
|
||||||
const { options, help } = parseArgs(argv);
|
const { options, help, version } = parseArgs(argv);
|
||||||
if (help) showHelp();
|
if (help) showHelp();
|
||||||
|
if (version) showVersion();
|
||||||
|
|
||||||
// If stdin has data, parse numbers and apply as positional overrides
|
// If stdin has data, parse numbers and apply as positional overrides
|
||||||
if (!process.stdin.isTTY) {
|
if (!process.stdin.isTTY) {
|
||||||
@@ -17,13 +18,13 @@ export async function main(): Promise<void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const results = generate(options);
|
for (const r of generate(options)) {
|
||||||
for (const r of results) {
|
console.log(options.decimals > 0 ? r.toFixed(options.decimals) : r);
|
||||||
console.log(options.decimals > 0 ? r.toFixed(options.decimals) : Math.floor(r));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
main().catch((err: Error) => {
|
main().catch((err: unknown) => {
|
||||||
console.error(`rand: ${err.message}`);
|
const msg = err instanceof Error ? err.message : String(err);
|
||||||
|
console.error(`rand: ${msg}`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
});
|
});
|
||||||
|
|||||||
+4
-4
@@ -1,11 +1,11 @@
|
|||||||
export async function readStdin(): Promise<string> {
|
export async function readStdin(): Promise<string> {
|
||||||
const decoder = new TextDecoder();
|
const decoder = new TextDecoder();
|
||||||
let buf = "";
|
const parts: string[] = [];
|
||||||
for await (const chunk of Bun.stdin.stream()) {
|
for await (const chunk of Bun.stdin.stream()) {
|
||||||
buf += decoder.decode(chunk, { stream: true });
|
parts.push(decoder.decode(chunk, { stream: true }));
|
||||||
}
|
}
|
||||||
buf += decoder.decode(); // flush
|
parts.push(decoder.decode()); // flush
|
||||||
return buf;
|
return parts.join("");
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Parse space-separated numbers from stdin. Returns number[] (may be empty). */
|
/** Parse space-separated numbers from stdin. Returns number[] (may be empty). */
|
||||||
|
|||||||
+6
-10
@@ -1,19 +1,15 @@
|
|||||||
export type Dist =
|
export const DISTS = [
|
||||||
| "uniform"
|
|
||||||
| "normal"
|
|
||||||
| "binomial"
|
|
||||||
| "poisson"
|
|
||||||
| "exponential"
|
|
||||||
| "hypergeometric";
|
|
||||||
|
|
||||||
export const DISTS: Dist[] = [
|
|
||||||
"uniform",
|
"uniform",
|
||||||
"normal",
|
"normal",
|
||||||
"binomial",
|
"binomial",
|
||||||
"poisson",
|
"poisson",
|
||||||
"exponential",
|
"exponential",
|
||||||
"hypergeometric",
|
"hypergeometric",
|
||||||
];
|
] as const;
|
||||||
|
|
||||||
|
export type Dist = (typeof DISTS)[number];
|
||||||
|
|
||||||
|
export const VERSION = "1.0.0";
|
||||||
|
|
||||||
export interface Options {
|
export interface Options {
|
||||||
dist: Dist;
|
dist: Dist;
|
||||||
|
|||||||
+3
-5
@@ -5,8 +5,6 @@
|
|||||||
"target": "ESNext",
|
"target": "ESNext",
|
||||||
"module": "Preserve",
|
"module": "Preserve",
|
||||||
"moduleDetection": "force",
|
"moduleDetection": "force",
|
||||||
"jsx": "react-jsx",
|
|
||||||
"allowJs": true,
|
|
||||||
"types": ["bun"],
|
"types": ["bun"],
|
||||||
|
|
||||||
// Bundler mode
|
// Bundler mode
|
||||||
@@ -23,8 +21,8 @@
|
|||||||
"noImplicitOverride": true,
|
"noImplicitOverride": true,
|
||||||
|
|
||||||
// Some stricter flags (disabled by default)
|
// Some stricter flags (disabled by default)
|
||||||
"noUnusedLocals": false,
|
"noUnusedLocals": true,
|
||||||
"noUnusedParameters": false,
|
"noUnusedParameters": true,
|
||||||
"noPropertyAccessFromIndexSignature": false
|
"noPropertyAccessFromIndexSignature": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user