Strict Mode
Sonamu enables all strict options.Copy
{
"compilerOptions": {
"strict": true, // Enable all strict options
"noImplicitAny": true, // Require explicit any type
"strictNullChecks": true, // Strict null/undefined checking
"strictFunctionTypes": true, // Strict function type checking
"strictBindCallApply": true, // Strict bind/call/apply checking
"strictPropertyInitialization": true, // Require class property initialization
"noImplicitThis": true, // Require explicit this type
"alwaysStrict": true // Auto-add 'use strict'
}
}
strict: true is a shortcut that enables all the above options at once.Strict Options in Detail
- noImplicitAny
- strictNullChecks
- strictFunctionTypes
- strictBindCallApply
- strictPropertyInitialization
- noImplicitThis
Require Explicit any Type
Function:- Prevents implicit use of
anywhen type cannot be inferred
Copy
// β Error
function log(message) {
// ^^^^^^^ Parameter 'message' implicitly has an 'any' type
console.log(message);
}
// β
Correct code
function log(message: string) {
console.log(message);
}
// β
Using generics
function log<T>(message: T) {
console.log(message);
}
If typing is difficult, use
unknown. Itβs safer than any.Strict null/undefined Checking
Function:- Prevents assigning
nullandundefinedto other types - All types are non-nullable by default
Copy
// β Error
let name: string = null;
// ^^^^ Type 'null' is not assignable to type 'string'
// β
Using union type
let name: string | null = null;
name = "Alice";
// β
Optional chaining
interface User {
email?: string;
}
function sendEmail(user: User) {
// β Error
const length = user.email.length;
// ^^^^^ Object is possibly 'undefined'
// β
Optional chaining
const length = user.email?.length;
// β
Null check
if (user.email !== undefined) {
const length = user.email.length;
}
}
Copy
// Entity defined via entity.json
interface User {
id: number;
name: string; // Cannot be null
bio: string | null; // Allows null
email?: string; // Allows undefined
created_at: Date;
}
// Ensure type safety in Model class
class UserModelClass extends BaseModelClass {
@api({ httpMethod: "GET" })
async findById(
subset: UserSubsetKey,
id: number
): Promise<UserSubsetMapping[typeof subset] | null> {
const rdb = this.getPuri("r");
const user = await rdb.table("users").where("id", id).first();
// strictNullChecks enforces null check
if (!user) {
return null;
}
return user;
}
}
Strict Function Type Checking
Function:- Checks contravariance of function parameters
- Safer function type compatibility
Copy
interface Animal {
name: string;
}
interface Dog extends Animal {
bark(): void;
}
// β Error (strictFunctionTypes: true)
let animalHandler: (animal: Animal) => void;
let dogHandler: (dog: Dog) => void;
animalHandler = dogHandler;
// ^^^^^^^^^ Type '(dog: Dog) => void' is not assignable to type '(animal: Animal) => void'
// β
Correct direction
dogHandler = animalHandler; // OK
This option significantly improves function type safety, but may have compatibility issues with some libraries.
Strict bind/call/apply Checking
Function:- Type checks parameters of
bind,call,applymethods
Copy
function greet(name: string, age: number) {
console.log(`Hello, ${name}! You are ${age} years old.`);
}
// β Error
greet.call(null, "Alice", "30");
// ^^^^ Argument of type 'string' is not assignable to parameter of type 'number'
// β
Correct types
greet.call(null, "Alice", 30);
// β Error
greet.apply(null, ["Alice"]);
// ^^^^^^^^^ Argument of type '[string]' is not assignable to parameter of type '[string, number]'
// β
Correct argument count
greet.apply(null, ["Alice", 30]);
Require Class Property Initialization
Function:- Verifies that class properties are initialized in the constructor
Copy
class User {
// β Error
name: string;
// ^^^^ Property 'name' has no initializer and is not definitely assigned in the constructor
// β
Initialized in constructor
email: string;
constructor(email: string) {
this.email = email;
}
// β
Default value provided
age: number = 0;
// β
Optional
bio?: string;
// β
Allows null
avatar: string | null = null;
// β
Definite assignment assertion (only when certain)
id!: number;
}
Copy
// Generated types are all initialized
interface UserRow {
id: number; // Auto-generated by DB
email: string; // Required field
name: string; // Default value "guest"
created_at: Date; // Default value CURRENT_TIMESTAMP
}
// Using Definite Assignment Assertion in Model class
class UserModelClass extends BaseModelClass {
// ! because initialized in constructor
private readonly tableName!: string;
constructor() {
super();
this.tableName = "users";
}
@api({ httpMethod: "POST" })
async create(data: UserSaveParams) {
// data must include required fields
const wdb = this.getDB("w");
return this.insert(wdb, data);
}
}
Require Explicit this Type
Function:- Error when
thistype is unclear
Copy
// β Error
function onClick() {
console.log(this.textContent);
// ^^^^ 'this' implicitly has type 'any'
}
// β
Explicit this type
function onClick(this: HTMLElement) {
console.log(this.textContent);
}
// β
Arrow function (no this binding)
const onClick = () => {
// Uses outer this
};
// β
Class method
class Button {
text: string = "Click me";
onClick() {
console.log(this.text); // OK
}
}
Additional Type Check Options
Sonamu enables additional check options beyond strict mode.Copy
{
"compilerOptions": {
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"useUnknownInCatchVariables": true,
"noUncheckedIndexedAccess": true
}
}
- Unused Check
- Return Check
- Switch Check
- Catch Variables
- Index Access
Warn About Unused Code
Copy
{
"noUnusedLocals": true,
"noUnusedParameters": true
}
Copy
// β noUnusedLocals error
function greet(name: string) {
const message = "Hello"; // Unused
// ^^^^^^^ 'message' is declared but its value is never read
return name;
}
// β noUnusedParameters error
function add(a: number, b: number) {
// ^ 'b' is declared but its value is never read
return a;
}
// β
Use _ to intentionally ignore
function onClick(_event: MouseEvent) {
console.log("Clicked!");
}
Parameters starting with
_ donβt trigger warnings.Require Return in All Code Paths
Copy
{
"noImplicitReturns": true
}
Copy
// β Error
function getStatus(code: number): string {
if (code === 200) {
return "OK";
} else if (code === 404) {
return "Not Found";
}
// ^^^^ Not all code paths return a value
}
// β
Return in all paths
function getStatus(code: number): string {
if (code === 200) {
return "OK";
} else if (code === 404) {
return "Not Found";
}
return "Unknown";
}
// β
Or explicit error
function getStatus(code: number): string {
if (code === 200) {
return "OK";
} else if (code === 404) {
return "Not Found";
}
throw new Error(`Unknown status code: ${code}`);
}
Prevent Switch Statement Fallthrough
Copy
{
"noFallthroughCasesInSwitch": true
}
Copy
// β Error
function getDay(day: number): string {
switch (day) {
case 0:
return "Sunday";
case 1:
console.log("Monday");
// ^^^^ Fallthrough case in switch
case 2:
return "Tuesday";
default:
return "Unknown";
}
}
// β
Add break or return
function getDay(day: number): string {
switch (day) {
case 0:
return "Sunday";
case 1:
console.log("Monday");
return "Monday"; // or break;
case 2:
return "Tuesday";
default:
return "Unknown";
}
}
catch Block Variables are unknown
Copy
{
"useUnknownInCatchVariables": true
}
Copy
try {
throw new Error("Something went wrong");
} catch (error) {
// error is unknown type
// β Error
console.log(error.message);
// ^^^^^ Object is of type 'unknown'
// β
Use type guard
if (error instanceof Error) {
console.log(error.message);
}
// β
Type assertion (only when certain)
const err = error as Error;
console.log(err.message);
}
Using
unknown instead of any enforces type checking.Require undefined Check for Array/Object Access
Copy
{
"noUncheckedIndexedAccess": true
}
Copy
const users = ["Alice", "Bob"];
// β Error (noUncheckedIndexedAccess)
const firstUser = users[0];
// ^^^^^^^^^ Type 'string | undefined'
const name = firstUser.toUpperCase();
// ^^^^^^^^^^^ Object is possibly 'undefined'
// β
undefined check
const firstUser = users[0];
if (firstUser !== undefined) {
const name = firstUser.toUpperCase();
}
// β
Optional chaining
const name = users[0]?.toUpperCase();
// β
Non-null assertion (only when certain)
const name = users[0]!.toUpperCase();
// Same for objects
type UserMap = { [key: string]: string };
const userMap: UserMap = { alice: "Alice" };
const user = userMap["alice"];
// ^^^^ Type 'string | undefined'
This option makes all array/object access return
T | undefined. May require many code changes.Stricter Options (Optional)
Options you can add as needed.Copy
{
"compilerOptions": {
"noPropertyAccessFromIndexSignature": true,
"exactOptionalPropertyTypes": true,
"noUncheckedSideEffectImports": true
}
}
noPropertyAccessFromIndexSignature
noPropertyAccessFromIndexSignature
Index signatures can only be accessed with bracketsWhen to use:
Copy
interface Options {
[key: string]: string;
}
const options: Options = { color: "red" };
// β Error
const color = options.color;
// β
Use brackets
const color = options["color"];
- When you want to make dynamic property access explicit
exactOptionalPropertyTypes
exactOptionalPropertyTypes
Optional properties only allow undefinedWhen to use:
Copy
interface User {
name?: string;
}
// β Error
const user: User = { name: undefined };
// β
Omit property
const user: User = {};
// β
To explicitly allow undefined
interface User {
name: string | undefined;
}
- When you want to strictly distinguish between optional and
| undefined
May have compatibility issues with many libraries.
Disabling Type Checks
Sometimes you need to bypass type checks in specific situations.- Entire File
- Single Line
- Type Assertion
Copy
// @ts-nocheck
// Ignore all type checks in this file
const value = "hello";
value = 123; // No error
Copy
// @ts-ignore
const value = someUntypedLibrary();
// Or add error explanation
// @ts-expect-error - External library type mismatch
const value = someUntypedLibrary();
Use
@ts-expect-error instead of @ts-ignore to get a warning when no error actually occurs.Copy
// Type assertion with as
const value = someValue as string;
// Non-null assertion
const element = document.getElementById("root")!;
// Double assertion (last resort)
const value = someValue as unknown as TargetType;
Type assertions break type safety. Use only when absolutely certain.
Recommended Settings
- New Projects
- Existing Projects
- Libraries
Copy
{
"compilerOptions": {
// Use Sonamu default settings
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"useUnknownInCatchVariables": true,
"noUncheckedIndexedAccess": true
}
}
Copy
{
"compilerOptions": {
"strict": true,
// Add gradually
// "noUnusedLocals": true,
// "noUncheckedIndexedAccess": true
}
}
strict: truenoUnusedLocals,noUnusedParametersnoImplicitReturnsnoUncheckedIndexedAccess(requires many changes)
Copy
{
"compilerOptions": {
"strict": true,
"declaration": true,
"declarationMap": true,
"noUnusedLocals": true,
"noUnusedParameters": true
}
}
Performance Considerations
More type check options can increase compilation time.Improving Type Check Speed
Improving Type Check Speed
Copy
{
"compilerOptions": {
"skipLibCheck": true, // Skip node_modules type check
"incremental": true, // Incremental compilation
"tsBuildInfoFile": ".tsbuildinfo"
}
}
- Donβt check
.d.tsfiles innode_modules - Significantly reduces compilation time
- Enabled by default in Sonamu
- Only recompile changed files
- Cache stored in
.tsbuildinfofile