Syntax reduction Examples All rules Install
What gets removed

~74% of TypeScript's syntax surface, yeeted.

By syntax construct count, ShotScript removes ~74% of what TypeScript allows. We count every distinct way to express the same thing — variable declaration forms, function syntaxes, error-handling patterns — and reduce each category to one. Some disappear entirely; none are banned because they're bad, only because fewer forms is the goal.

OOP
class · this · extends · decorators · abstract
all of it
Error handling
try · catch · finally · throw
all of it
Type constructs
interface · enum · & · conditional · mapped · infer · any · void · undefined
~65%
Function syntax
arrow functions · generators · overloads · default params · optional params
~50%
Control flow
ternary · do-while · for-in · labels
~40%

Every TypeScript form, mapped

kept in ShotScript removed — click to see replacement

Examples

What linting looks like.

Type structure

TypeScript
type Role = 'admin' | 'member' | undefined

interface Timestamps {
  createdAt: Date
  updatedAt?: Date
}

interface BaseUser {
  id: number
  email: string
  avatar?: string | null
  metadata?: Record<string, any>
}

type User = BaseUser & Timestamps & {
  role?: Role
  deletedAt?: Date | null
}

// role: 4 states · 4 null forms · fields across 3 decls
ShotScript
type Role = 'admin' | 'member'

type Timestamps = {
  readonly createdAt: Date
  readonly updatedAt: Date | null
}

type BaseUser = {
  readonly id: number
  readonly email: string
  readonly avatar: string | null
  readonly metadata: Record<string, unknown>
}

type User = {
  readonly base: BaseUser
  readonly timestamps: Timestamps
  readonly role: Role | null
  readonly deletedAt: Date | null
}

// role: 3 states · 1 null form · all fields explicit

Async + error handling

TypeScript
async function getUser(
  id?: number
): Promise<User | undefined> {
  if (!id) return undefined
  try {
    const res = await fetch(`/users/${id}`)
    if (!res.ok) throw new Error(
      `HTTP ${res.status}`
    )
    const data = await res.json() as any
    return data as User
  } catch (e) {
    console.error(e)
    return undefined
  }
}

// failure is invisible to callers · `as` silences the checker
ShotScript
import { safeFetch, jsonParse } from 'shotscript/std'
import type { PromiseResult } from 'shotscript/std'

async function getUser(
  id: number
): PromiseResult<User> {
  const [res, fetchErr] = await safeFetch(
    `/users/${id}`
  )
  if (fetchErr !== null) return [null, fetchErr]
  return jsonParse<User>(await res.text())
}

// every failure is in the signature · no `as`, no `throw`

All rules

Functions

5 rules
no-arrow-functions
Arrow functions are banned. Use named function declarations or expressions — they show up in stack traces, are grep-able, and are testable in isolation.
require-named-functions
Function expressions passed as arguments must be named. Anonymous callbacks disappear in stack traces.
require-explicit-return-type
Every function declaration must have an explicit return type annotation. Inference hides contracts from callers.
no-default-parameter
Default parameters are banned. Accept T | null and handle the null branch explicitly inside the body.
no-async-without-await
An async function with no await is banned. Remove async and return Result<T> instead of PromiseResult<T>.

Error handling

7 rules
no-throw
Throwing is banned. Return [null, new Error(...)] — errors belong in the type signature, not in hidden control flow.
no-try
try/catch is banned. Wrap third-party throwing code with toResult or toPromiseResult from shotscript/std.
no-promise
new Promise() and Promise.resolve/reject/all/race/any/allSettled() are banned. Use toPromiseResult() to wrap external Promise-returning functions.
no-promise-chain
.then() and .catch() chains are banned. Use await with tuple destructuring.
no-floating-promises
Every Promise-returning call must be awaited or explicitly discarded with void fn(). Unhandled promises silently swallow errors.
require-tuple-destructure
Calls to functions that return a [T | null, E | null] tuple must be destructured immediately — const [val, err] = fn().
require-async-tuple-return
Async functions must return Promise<void>, PromiseResult<T, E>, or an explicit tuple form. A bare Promise<User> hides the failure path.

Variables

7 rules
no-var
var is banned. Use const.
no-multi-var-decl
Multiple declarators in one statement (const a = 1, b = 2) are banned. One declaration per statement.
no-increment-decrement
++ and -- are banned. Use += 1 and -= 1.
no-shadow
Variable shadowing is banned. Inner scopes cannot declare names that already exist in outer scopes.
no-param-reassign
Reassigning function parameters is banned. Use a new const with a distinct name.
no-multi-assign
Chained assignment (a = b = 1) is banned.
no-return-assign
Assignment inside a return statement is banned.

Control flow

18 rules
no-ternary
Ternary expressions are banned. Use if/else or extract a named function.
no-and-shorthand
condition && doThing() for side effects is banned. Use if (condition === true) { doThing() }.
no-or-shorthand
condition || doThing() for side effects is banned. Use an if block.
no-implicit-truthy
Conditions must be boolean-typed. Write if (x !== null), not if (x) — implicit truthy is banned.
no-unnecessary-condition
A === null check on a value whose type can never be null is banned — it's dead code the type system can prove unreachable.
no-loose-equality
== and != are banned. Use === and !== only.
no-for-in
for...in is banned. Use for...of Object.keys() or for...of Object.entries().
no-do-while
do...while is banned. Use a while loop.
no-labels
Labelled statements and break/continue with labels are banned. Extract a function and return instead.
switch-no-fallthrough
Every switch case must end with break, return, or throw. Implicit fallthrough is banned.
no-logical-assignment
Logical assignment operators (??=, ||=, &&=) are banned.
no-destructuring-default
Defaults inside destructuring patterns (const { x = 5 } = obj) are banned. Use explicit null checks.
no-generators
Generator functions (function*) and yield are banned.
no-loop-func
Function declarations or expressions inside loops are banned — they close over a mutable loop variable.
no-eval
eval() is banned.
no-self-compare
Comparing a value to itself (x === x) is always a bug.
no-self-assign
Assigning a variable to itself (x = x) is always a bug.
no-return-await
return await x is redundant; use return x. Since no-try bans try blocks, there is no case where return await changes behavior.

Types

37 rules
no-interface
Interfaces are banned. Use type exclusively — one way to define a shape.
no-class
Classes are banned. Use a plain type for data and plain functions for behaviour.
no-abstract
abstract classes and members are banned.
no-enum
enum is banned. Use an as const object and a typeof type alias instead.
no-any
any is banned. Use unknown and narrow explicitly.
no-assertion
Type assertions (value as T) are banned. Parse and validate at boundaries; return a result tuple.
no-non-null
Non-null assertions (value!) are banned. Check for null explicitly.
no-ts-comment
@ts-ignore, @ts-expect-error, and @ts-nocheck are banned. Fix the type error.
no-undefined-type
undefined in type annotations is banned. The only nullable value is null.
no-optional-property
Optional properties (prop?: T) are banned. Use prop: T | null.
no-optional-parameter
Optional parameters (x?: T) are banned. Use x: T | null.
require-readonly-property
Every object type property must be readonly. Mutation is explicit, not the default.
require-readonly-arrays
Array type annotations must use readonly T[]. Not T[], not Array<T>.
require-readonly-collections
Map<K, V> and Set<T> in type positions are banned. Use ReadonlyMap<K, V> and ReadonlySet<T>.
no-array-generic
Array<T> and ReadonlyArray<T> in annotations are banned. Use readonly T[].
no-intersection-types
Intersection types (A & B) are banned. Spell out the combined fields, or compose by value.
no-conditional-type
Conditional types (T extends U ? X : Y) are banned.
no-mapped-type
Mapped types ({ [K in keyof T]: ... }) are banned.
no-template-literal-type
Template literal types (` prefix-${T} `) are banned.
no-infer
infer inside conditional types is banned.
no-variadic-tuple
Variadic tuple types ([...T]) are banned.
no-anonymous-tuple
Tuple elements must be named: [value: T, err: E] not [T, E].
no-index-signature
Index signatures ([k: string]: T) are banned. Use Map<K, V>.
no-banned-utility-types
Partial, Required, Record, InstanceType, ConstructorParameters, and ThisType are banned. Spell out the shape.
no-readonly-wrapper
Readonly<T> wrapper is banned. Mark each property readonly directly.
no-primitive-wrapper-types
Boxed primitive types (String, Number, Boolean, Symbol, BigInt) are banned. Use the lowercase primitives.
no-constructor-type
Constructor type signatures (new (...args): T) are banned.
no-function-type
The Function type is banned. Use an explicit call signature type.
no-object-type
The object type is banned. Use an explicit type shape.
no-empty-object-type
The empty object type {} in annotations is banned.
no-symbol-type
symbol and unique symbol are banned. Their two practical uses — branded types and well-known symbols (Symbol.iterator etc.) — are both unavailable in ShotScript: branding uses a string phantom field ({ readonly __brand: B }), and generators/iterables are banned.
no-literal-boolean-type
true | false is banned — it is just boolean.
no-overloads
Function overload signatures are banned. Use a union parameter type and handle variants inside the body.
no-namespace
TypeScript namespaces and modules (namespace / module declarations) are banned.
no-decorators
Decorators are banned.
no-this
this is banned in all forms — no method context, no this parameters.
require-readonly-parameters
Inline array and object-literal parameter types must be readonly — xs: readonly number[] and { readonly x: number }.

Imports & exports

7 rules
no-default-export
Default exports are banned. Named exports only — every export is addressable and tree-shakeable.
no-require
require() is banned. ESM import only.
no-index-import
Importing from an index file or a bare directory is banned. Import the specific file.
no-useless-empty-export
Empty export {} statements that serve no purpose are banned.
no-export-star
export * from './x' and export * as ns from './x' are banned; use named re-exports.
no-side-effect-import
Bare side-effect imports (import './setup' with no bindings) are banned; name what you import.
no-dynamic-import
Dynamic import(...) expressions are banned; use a static import.

Style & clarity

29 rules
prefer-template
String concatenation with + is banned when a template literal would work. Use ` hello ${name} `.
no-sparse-arrays
Sparse array literals ([1,,3]) are banned. Use null for an explicit empty slot: [1, null, 3].
no-double-bang
!!value is banned. Use Boolean(value).
no-unary-plus
Unary + for coercion is banned. Use Number(value).
no-parse-number-fns
parseInt and parseFloat are banned. Use Number(str).
no-throwing-globals
JSON.parse, JSON.stringify, fetch, structuredClone, BigInt(), new RegExp(), and new Date() are banned. Use the safe wrappers from shotscript/std.
no-metaprogramming-globals
Proxy, Reflect, Object.assign, Object.create, Object.defineProperty, and similar metaprogramming APIs are banned.
no-new-wrappers
new String(), new Number(), new Boolean() are banned.
no-new-user-types
new on user-defined constructors is banned. No classes means no new.
no-delete
The delete operator is banned. Build a new object without the key.
no-in
The in operator is banned. Use explicit property checks or discriminated unions.
no-bitwise
Bitwise operators (&, |, ^, ~, <<, >>, >>>) are banned.
no-comma-operator
The comma operator is banned.
no-arguments
The arguments object is banned. Use explicit rest parameters.
no-void
void expr is banned except as an explicit promise discard: void someCall() is the only permitted form.
no-tagged-templates
Tagged template literals are banned.
no-empty
Empty block bodies ({}) on functions, if, while, etc. are banned.
no-empty-pattern
Empty destructuring patterns (const {} = x, const [] = x) are banned.
no-lone-blocks
Standalone block statements ({ ... } not attached to control flow) are banned.
no-unused-expressions
Expressions whose result is not used are banned. Every expression must be assigned, returned, or have a deliberate side effect.
no-useless-rename
Renaming an import or export to the same name (import { x as x }) is banned.
no-useless-return
A bare return at the end of a void function is banned.
no-useless-concat
Concatenation of two string literals ("a" + "b") is banned — just write "ab".
no-useless-computed-key
Computed property keys that are string literals ({ ["x"]: 1 }) are banned.
no-mutating-array-methods
.sort(), .reverse(), .splice(), .push(), .pop(), .shift(), .unshift(), .fill(), .copyWithin() are banned. Use ES2023 immutable alternatives: toSorted, toReversed, toSpliced, with, or spread.
no-object-assign
Object.assign(...) is banned; use object spread { ...a, ...b } instead.
no-object-literal-accessors
get/set accessors in object literals are banned; use plain properties or functions.
no-restricted-globals
Legacy global functions isNaN, isFinite, and hasOwnProperty are banned. Use Number.isNaN, Number.isFinite, and Object.hasOwn.
no-prototype-method-call
.hasOwnProperty(), .isPrototypeOf(), .propertyIsEnumerable() as method calls are banned. Use Object.hasOwn etc.

Install
Install with AI — paste into Claude Code, Cursor, or any AI assistant
Add ShotScriptLint to this project: run npm install --save-dev shotscript, then add {"name": "shotscript/plugin"} to the plugins array in compilerOptions in tsconfig.json. Full ShotScript setup →
Manual setup
tsconfig.json
// tsconfig.json
{
  "compilerOptions": {
    "plugins": [{ "name": "shotscript/plugin" }]
  }
}
Run
$ npx shotscript 'src/**/*.ts'

Exits 0 when clean, 1 on violations. Add --json for machine-readable output.