Contributors · Style

Code conventions

These conventions describe the patterns used throughout the TypeScript rewrite and the expectations that make changes easier to review. They complement the compiler, linter, and tests; they do not replace them.

Match the subsystem you are changing. Nerdamer has a long history and not every file follows every newer convention perfectly. Prefer the established pattern in the surrounding code unless there is a specific reason to improve it as part of the same change.

Reuse before adding

Before adding a helper, utility, class, or new file, search the codebase for an existing abstraction that already performs the operation or can be extended cleanly.

1 · Searchexisting function / class / pattern

Look in the owning subsystem and shared utilities first.

2 · Extendreuse the existing abstraction

Prefer one implementation with a broader supported case over a second parallel implementation.

3 · Add only when needednew helper / file

Create something new when it has a clear responsibility that does not belong in an existing abstraction.

Keep the change in the owning subsystem

A parser problem should normally be fixed in parser logic, a TeX problem in converters, and an algebra problem in algebra. Avoid compensating for a lower-level defect in several callers when the underlying representation or operation can be corrected once.

Prefer one clear return path

Many Nerdamer functions build a result in a variable named retval and return once at the end. This makes complicated symbolic branches easier to inspect while debugging.

Preferred for multi-branch logic
function operation(x: Expression): Expression {
  let retval: Expression;

  if (x.isNUM()) {
    retval = handleNumber(x);
  } else {
    retval = handleSymbolic(x);
  }

  return retval;
}
Use judgment for trivial functions
function square(x: number): number {
  return x * x;
}

The goal is readable control flow, not a mechanical ban on every early return. Small guards and simple functions can stay simple.

Do not weaken types to make an error disappear

TypeScript is part of Nerdamer’s design. When a type error exposes a mismatch between parser entities, containers, expressions, or APIs, resolve the mismatch rather than broadening the type to any or forcing an unsafe cast.

Narrow explicitly

Use the relevant type guard before calling methods that only exist on Expression, Equation, Matrix, or another parser entity.

Keep public types accurate

A public method should expose the value it really accepts or returns, not the type that is easiest for one implementation branch.

Prefer existing aliases

Reuse project types such as ParserEntity, ExpressionInput, and option aliases instead of recreating overlapping local unions.

Preserve parser-entity distinctions

Not every parsed value is an Expression. Equations, vectors, matrices, sets, collections, and dictionaries are real result types. Do not silently coerce one into an Expression merely to reuse an API unless that conversion is part of the documented behavior.

Use named constants and existing symbolic shortcuts

When the codebase already has a named constant, enum value, parser constant, or preconstructed symbolic shortcut, use it instead of repeating a magic string or constructing the same symbolic value ad hoc.

// Prefer the established project value when one exists.

const value = two();

// Avoid repeating an equivalent construction throughout the codebase.

const value = Expression.create(2);

This is especially useful for parser names, expression groups, operator names, common numeric values, and settings shared across modules.

Access modifiers should describe intended use

Do not make a method private simply because there is only one caller today. A method should be private when it belongs solely to the implementation of its declaring class. Keep reusable or subclass-facing behavior public or protected as appropriate.

Comments should survive the change

If you modify code that already has explanatory comments, preserve them or revise them so they remain true. Do not remove useful context simply because the implementation changed.

For new comments, explain the non-obvious reason or mathematical step rather than narrating the next line.

Tests should protect the behavior that failed

For a bug fix, add a targeted regression that reproduces the original failure directly. Prefer a small test that clearly states the expected mathematical or API behavior over a broad snapshot that could pass for the wrong reason.

Reproduce

Start with the smallest expression or API call that demonstrates the problem.

Fix

Change the subsystem that owns the behavior rather than patching individual callers.

Protect

Keep the reproducer as a spec so a future refactor cannot silently restore the regression.

Be conservative with files and exports

New files, barrel exports, and public symbols add complexity to the project. Introduce them when they clarify the structure or expose an API intended for ongoing support, not simply to make a local change appear cleaner.

Likewise, do not delete apparently unused code until you have checked for public exports, parser registration, string dispatch, reflection, compatibility hooks, test-only use, or library utilities.

Validate at the right levels

npm run typecheck
npm test
npm run build

Use targeted tests while iterating, then run the full checks before considering a change complete. If the change affects generated documentation or package exports, run the corresponding documentation or package-validation commands as well.

Quick review checklist

Area Check
Reuse Did you search for an existing implementation before adding another helper or file?
Types Did you solve the type mismatch rather than hide it with a broader type or unsafe cast?
Entities Are Expression, Equation, Matrix, Vector, and other parser result types kept distinct where required?
Constants Are existing named constants, enums, and symbolic shortcuts reused?
Comments Are existing comments still accurate, and do new comments explain something non-obvious?
Tests Does a regression test directly protect the behavior that was fixed?
Public API scope Are new files, exports, and public methods actually necessary?
Development workflow

Setup, scratch-file use, tests, type checking, and builds are covered separately.

How to develop