Skip to content

Whyjs-Living/whyjs

WhyJS

The question that fixes JavaScript.

WhyJS is a strict superset of JavaScript that compiles to standard ES2022. It fixes JavaScript's most notorious design flaws — nil confusion, loose equality, implicit globals, silent NaN propagation, and more — without breaking ecosystem compatibility. Rename any .js file to .why and it compiles. New safety features are additions, never replacements. The mantra: layer on top, never replace.

Installation

npm install -g whyjs

For development:

git clone https://github.com/4shut0sh/whyjs.git
cd whyjs
npm install
npm run build
npm link

Requires Node.js >= 20.

Quick Start

Create hello.why:

val name = 'world'

fn greet(who: string): string {
  return 'Hello, ' + who + '!'
}

val msg = greet(name)
console.log(msg)

Compile and run:

why build hello.why
node hello.js
# Hello, world!

In 30 seconds you've used val (immutable binding), type annotations, fn declarations, and the WhyJS compiler.

Why WhyJS?

JavaScript has 9 fundamental problems that cause countless bugs and wasted hours. WhyJS fixes them.

# JavaScript Problem WhyJS Fix
1 null vs undefined confusion Unified as nil. null, undefined, and nil all compile to undefined.
2 let vs const vs var ambiguity val = immutable (default), let = mutable, const = deep constant. var emits a warning.
3 == coercion bugs == is a parse error. Only === exists. why migrate auto-fixes legacy code.
4 number is one type int, float, decimal are compile-time annotations. In Wasm targets they become hard types.
5 NaN !== NaN footgun NaN is a NumberError in the type system. The checker prevents silent propagation.
6 [10, 9, 1].sort()[1, 10, 9] Compiler warns on .sort() without comparator on numeric arrays and emits (a, b) => a - b.
7 parseInt("08") is octal maybe parseInt without a radix is a compile-time error.
8 CommonJS / ESM confusion ESM-only. require emits a warning; why migrate rewrites to import.
9 Unhandled promise rejections A floating async call without await, .catch(), or assignment is a compile-time error (E004).

Features

Nil Safety

fn process(user: User?): string {
  return user.name;            // ✗ Compile error E042
  return user?.name ?? 'Anonymous'; // ✓ Safe
}

Default non-nullable. Use T? for T | nil. Flow-sensitive narrowing with if (x !== nil).

Actor Model

actor Counter {
  mut count = 0;

  pub async fn increment(): int {
    self.count = self.count + 1;
    return self.count;
  }
}

val counter = new Actor(Counter);
val value = await counter.increment();

Actors run in Web Workers with message-passing RPC. Only pub methods are callable from outside. All messages are validated for serializability. Default 30-second timeout on operations.

WebAssembly AOT

@wasm({ memory: 512 })
fn processImage(pixels: int[], width: int, height: int): int[] {
  return pixels;
}

val result = await processImage(myPixels, 800, 600);

Functions annotated with @wasm are compiled to run in a sandboxed Worker. Only primitive types (int, float, decimal, bool) and typed arrays are allowed.

Tree-Shaken Standard Library

import { groupBy, debounce } from '@why/std';

val grouped = groupBy(users, u => u.role);
val safeFn = debounce(expensiveOp, 300);

Only imported functions are inlined into your bundle. Zero dead code.

Zero-Config Migration

why migrate ./src           # convert JS to WhyJS
why migrate ./src --dry-run # preview changes

Transforms: null/undefinednil, =====, varval/let, thisself, parseInt → add radix, new Date()Temporal, arguments → rest params, eval → marked, and more.

CLI Reference

Command Description
why build <file> Compile .why to ES2022 JavaScript
why migrate <dir> Convert .js files to .why semantics
why test <dir> Run Vitest in the given directory
why dev <file> Watch mode — recompile on change
why fmt <dir> Opinionated formatter (2 spaces, single quotes, semicolons)
why lint <dir> Lint and typecheck all .why files
why bench <file> Run benchmarks with statistical output
why lsp Start the Language Server Protocol (stdio)
why selfhost <dir> Analyze TypeScript for WhyJS self-hosting gaps

why build

why build app.why                    # writes app.js next to source
why build app.why -o dist/           # custom output directory
why build app.why -t wasm            # print @wasm function listing to stderr
why build app.why --stdout           # print JS to stdout

Targets: node (default), browser, wasm.

why fmt

why fmt ./src            # format all .why files in place
why fmt ./src --check    # exit 1 if any file would change
why fmt ./src --dry-run  # alias for --check

Opinionated: 2-space indent, single quotes, semicolons, width 100. Formatted output is re-parsed to verify correctness.

why lint

why lint ./src                   # stylish output (default)
why lint ./src --format json     # JSON output
why lint ./src --format compact  # one-line-per-diagnostic

Runs 10+ lint rules (L001–L010) alongside the type checker. Rules include: no-var, prefer-val, no-eval, no-console, require-radix, unused variables, magic numbers, and more.

why bench

why bench bench.why                    # default: 3 warmup, 30s timeout
why bench bench.why --iterations 1000  # fixed iteration count
why bench bench.why --warmup 5         # warmup runs before measurement
why bench bench.why --timeout 60000    # timeout per benchmark (ms)

Outputs statistical summary: mean, median, min, max, standard deviation.

why selfhost

why selfhost ./src                     # write SELF_HOSTING_GAP.md
why selfhost ./src --output gap.md     # custom output path

Analyzes TypeScript source code and generates a gap report showing which features are needed for the WhyJS compiler to be rewritten in WhyJS itself.

why lsp

The Language Server Protocol server provides IDE features for .why files:

  • Diagnostics — real-time error and warning detection as you type (300ms debounce)
  • Hover — type information for any symbol
  • Completion — keywords, @std functions, nil-safety patterns
  • Code Actions — quick fixes for parseInt radix, nil checks, =====
  • Go to Definition — jump to top-level declarations

Language Reference

Keywords

Keyword Purpose
val Immutable binding (compiles to const)
let Mutable binding (compiles to let)
mut Mutable state in actors (mut x = 0)
const Deep constant (compiler error on mutable properties)
var Legacy — compiles with a warning
fn Function declaration
async fn Async function declaration
nil Absence of value (compiles to undefined)
actor Concurrent actor declaration
pub Public actor method
self Reference to current instance (compiles to this)
using Deterministic resource cleanup
test Inline test block (compiles to Vitest test())
import / export ESM modules
if / else / while Control flow
try / catch / finally Error handling
return Return from function
class / new Object-oriented (standard JS semantics)
await Await a promise

Decorators

Decorator Purpose
@wasm Mark a sync fn for WebAssembly compilation
@wasm({ memory: N }) WebAssembly with memory limit (MB)
@MainActor Guarantee function runs on the main/UI thread

Type Annotations

val name: string = 'Alice'
val age: int = 30
val score: float = 9.5
val precise: decimal = 0.1
val active: bool = true
val missing: nil = nil

val maybe: string? = nil          // optional (nullable)
val nums: int[] = [1, 2, 3]      // array
val either: int | string = 42    // union

Primitive types: string, int, float, decimal, bool, nil.

Type modifiers: T? (optional), T[] (array), T | U (union).

All type annotations are erased in JS output. In --target wasm they become hard types.

Operators

  • === / !== — strict equality (only option; == is a parse error)
  • ?? — nil coalescing
  • ?. — optional chaining
  • ! (postfix) — non-null assertion (emits warning)
  • +, -, *, /, %, ** — arithmetic
  • &&, || — logical
  • <, >, <=, >= — comparison

Actor Model

WhyJS provides a built-in actor model for structured concurrency. Actors run in Web Workers with message-passing RPC.

Declaring an Actor

actor Counter {
  mut count = 0;

  pub async fn increment(): int {
    self.count = self.count + 1;
    return self.count;
  }

  pub async fn getCount(): int {
    return self.count;
  }

  async fn reset(): nil {
    self.count = 0;
  }
}

Actors have mut state fields and async fn methods. Only pub methods are callable from outside; private methods are internal only. The self keyword accesses the actor's state.

Creating and Using an Actor

val counter = new Actor(Counter);

async fn main(): nil {
  val n = await counter.increment();
  val c = await counter.getCount();
  console.log(c);
}

new Actor(X) creates an actor instance. All method calls return promises and must be awaited.

Structured Concurrency

val task = Actor.spawn(async (signal) => {
  return await fetchData(signal);
});
task.cancel();

val results = await Actor.all([task1, task2, task3]);

@MainActor

Functions decorated with @MainActor are guaranteed to run on the main thread:

@MainActor
fn updateUI(msg: string): nil {
  console.log(msg);
}

@std Library

The WhyJS standard library is tree-shaken at compile time — unused functions add zero bytes to output. Import from @std/* or @why/std/*.

@std/collections

Function Signature Description
groupBy (arr, key) => Record<string, T[]> Group array elements by key function
keyBy (arr, key) => Record<string, T> Index array by key function (last wins)
zip (a, b) => [A, B][] Pair elements from two arrays
range (start, end, step?) => number[] Half-open range [start, end) (max 10M)
chunk (arr, size) => T[][] Split array into fixed-size chunks
shuffle (arr) => T[] Fisher-Yates shuffle (crypto-seeded)

@std/objects

Function Signature Description
deepEqual (a, b) => boolean Deep structural equality (handles cycles)
deepClone (obj) => T Deep clone (uses structuredClone when available)
omit (obj, keys) => Omit<T, K> Return object without specified keys
pick (obj, keys) => Pick<T, K> Return object with only specified keys
merge (target, ...sources) => T Shallow merge (blocks __proto__ pollution)

@std/strings

Function Signature Description
camelCase (s) => string Convert to camelCase
kebabCase (s) => string Convert to kebab-case
truncate (s, len) => string Truncate with ellipsis (max 10K)
slugify (s) => string URL-safe slug (max 200 chars)

@std/async

Function Signature Description
delay (ms) => Promise<void> Sleep for ms milliseconds (max 30s)
timeout (promise, ms) => Promise<T> Reject if promise doesn't resolve in ms
retry (fn, attempts, delayMs?) => Promise<T> Retry async function (max 10 attempts)
debounce (fn, ms) => T Debounce function calls
throttle (fn, ms) => T Throttle function calls
memoize (fn, maxSize?) => T Cache results (max 1000 entries default)

@std/numbers

Function Signature Description
clamp (value, min, max) => number Clamp value to [min, max] range
inRange (value, min, max) => boolean Check if value is in range
random (min, max) => number Crypto-secure random number
sumPrecise (nums) => number Kahan summation for floating-point stability
mean (nums) => number Arithmetic mean
median (nums) => number Median value
sortNumeric (arr) => number[] Numeric ascending sort (returns new array)

@std/validate

Function Signature Description
isEmail (s) => boolean Validate email format
isURL (s) => boolean Validate HTTP/HTTPS URL
isUUID (s) => boolean Validate UUID v1-v5 format
isEmpty (s) => boolean True if null, undefined, or empty string

@std/log

Function Signature Description
log.info (data) => void Structured JSON log at info level
log.warn (data) => void Structured JSON log at warn level
log.error (data) => void Structured JSON log at error level

@std/bench

Function Signature Description
bench (label, fn, iterations?) => BenchResult Benchmark a function (max 10M iterations)

Run benchmarks with why bench file.why to get a formatted results table with mean, median, min, max, and stddev.

Diagnostic Codes

Errors

Code Name Description
E001 Removed equality == is not allowed; use ===
E002 Type mismatch int and float mixed without explicit conversion
E003 Implicit global Undeclared variable or assignment to undeclared name
E004 Unhandled promise Floating async call without await, .catch(), or assignment
E005 parseInt radix parseInt called without radix argument
E006 Numeric sort .sort() on numeric array without comparator
E042 Nil access Member access on nullable type without guard
E043 Nullable to non-null Passing nullable where non-null type is required
E044 MainActor await @MainActor function must be awaited from actor code
E045 Unknown std export Import of nonexistent function from @std/*
E046 Wasm async @wasm cannot be used with async fn
E047 Wasm untyped @wasm requires explicit parameter and return types
E048 Wasm DOM @wasm cannot access document, window, or DOM APIs
E049 Wasm await await is not allowed inside @wasm functions
E050 Wasm + MainActor @wasm and @MainActor cannot be combined
E100 MainActor no await @MainActor call without await
E101 Self outside actor self used outside actor context
E102 Actor call no await Actor method call without await
E103 Circular message Circular message dependency between actors
E104 Non-serializable msg Message contains non-serializable data
E105 Missing timeout Actor operation missing timeout
E106 Private access Access to private (non-pub) actor method
E200 Wasm unsupported param @wasm parameter has unsupported type
E201 Wasm unsupported return @wasm return type is unsupported
E202 Wasm unsupported op Unsupported operation in @wasm function
E203 Wasm non-wasm call Call to non-@wasm function from @wasm body
E204 Wasm memory limit @wasm memory exceeds allowed range
E205 Wasm self/this self/this not allowed in @wasm functions

Lint Rules

Code Name Description
L001 Sort comparator .sort() called without comparator
L002 Unused variable Declared but never used
L003 Deprecated API Usage of deprecated function
L004 Magic number Numeric literal that should be a named constant
L005 Empty catch catch block with no handling
L006 No var var usage — use val or let
L007 Prefer val let that is never reassigned
L008 No eval eval() usage is a security risk
L009 No console console.log in production — use @std/log
L010 Require radix parseInt without radix argument

Project Layout

src/
  ast/nodes.ts           — AST node classes (Statement, Expression, TypeAnnotation)
  compiler/
    lexer.ts             — Tokenizer (keywords, operators, nil/null/undefined unification)
    parser.ts            — Recursive descent parser → AST
    typechecker.ts       — Type inference, nil safety, diagnostic errors
    codegen.ts           — AST → ES2022 JavaScript
    diagnosticCodes.ts   — Stable error/warning codes
    prettyPrint.ts       — AST → formatted .why source
    wasm.ts              — @wasm constraint checking and stub generation
    wasm-validate.ts     — WebAssembly binary validation
    hover.ts             — Hover type info for LSP
    index.ts             — compile() / compileAll() / compileForBench()
  cli/
    index.ts             — Commander.js CLI entry
    migrate.ts           — JS → .why migration engine
    lint.ts              — Directory-wide lint + typecheck
    fmt.ts               — Formatter
    dev.ts               — Watch mode
    bench.ts             — Benchmark runner
    selfhost.ts          — Self-hosting gap analysis
  runtime/actor.ts       — Actor Worker RPC, spawn, all, @MainActor bridge
  stdlib/                — Runtime implementations of @std/* functions
  std/inlined.ts         — Compile-time inlined versions for tree-shaking
  lsp/server.ts          — Language server (stdio)
  errors/DiagnosticError.ts — Rust-style diagnostic formatting
  utils/security.ts      — Path validation, source size limits, type depth checks
editors/
  vscode-whyjs/          — VS Code extension with syntax highlighting and LSP
tests/                   — Vitest test suite
  e2e/                   — End-to-end integration tests
  cli/                   — CLI command tests

Security

WhyJS enforces security at every level of the compiler pipeline:

  • No eval or dynamic code execution in the compiler itself
  • Path traversal protection on all file operations
  • Source size limits (10MB) to prevent denial-of-service
  • Token count limits in the lexer
  • Recursion depth limits (100) in the type checker
  • Actor message serialization validation (no functions, no cycles)
  • Actor operation timeouts (default 30s)
  • Prototype pollution protection in @std/objects.merge
  • Crypto-secure randomness in @std/numbers.random and @std/collections.shuffle
  • ReDoS-safe regex patterns in @std/strings
  • Wasm memory limits (configurable, max 4096MB)
  • LSP document size limits (5MB) with debounced validation

Roadmap

  • Full JavaScript supersetfor, switch, class bodies, destructuring, spread, template ${...} interpolation
  • Real WebAssembly binary output@wasm functions emit .wasm instead of asm.js stubs
  • Generics<T> type parameters for type-safe collections and AST
  • Ohm (PEG) parser — For Why-only syntax extensions layered on TypeScript's grammar
  • esbuild bundler integration — Tree-shake @std/* at the bundle level
  • bound keyword — Auto-bind class methods to their instance
  • Temporal built-in — Full Temporal API as a first-class primitive
  • Self-hosting — Compile the WhyJS compiler in WhyJS

Contributing

See CONTRIBUTING.md for development setup and guidelines.

License

MIT — see LICENSE for details.

Acknowledgments

WhyJS was built in deep collaboration with AI systems. See CREDITS.md for a complete list of contributors.

About

JavaScript, fixed. A JS superset that compiles to standard ES2022.

Resources

License

Code of conduct

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors