Ban the null literal via ESLint and replace existing usages with undefined or truthiness checks (#4401)

This commit is contained in:
Keavon Chambers
2026-08-03 23:47:29 -07:00
committed by Dennis Kobert
parent 360321e0ce
commit 6480de274e
4 changed files with 17 additions and 11 deletions

View File

@@ -73,6 +73,13 @@ export default defineConfig([
], ],
}, },
], ],
"no-restricted-syntax": [
"error",
{
selector: "Literal[raw='null']",
message: "Use `undefined` instead of `null`.",
},
],
// TypeScript plugin config (for TS-specific linting) // TypeScript plugin config (for TS-specific linting)
"@typescript-eslint/indent": "off", "@typescript-eslint/indent": "off",
@@ -96,7 +103,7 @@ export default defineConfig([
"@typescript-eslint/consistent-type-assertions": ["error", { assertionStyle: "never" }], "@typescript-eslint/consistent-type-assertions": ["error", { assertionStyle: "never" }],
"@typescript-eslint/consistent-indexed-object-style": ["error", "record"], "@typescript-eslint/consistent-indexed-object-style": ["error", "record"],
"@typescript-eslint/consistent-generic-constructors": ["error", "constructor"], "@typescript-eslint/consistent-generic-constructors": ["error", "constructor"],
"@typescript-eslint/no-restricted-types": ["error", { types: { null: "Use `undefined` instead." } }], "@typescript-eslint/no-restricted-types": ["error", { types: { null: "Use `undefined` instead of `null`." } }],
// Prettier plugin config (for validating and fixing formatting) // Prettier plugin config (for validating and fixing formatting)
"prettier/prettier": "error", "prettier/prettier": "error",

View File

@@ -28,7 +28,7 @@ export function createSRgba8FromHsva(h: number, s: number, v: number, a: number)
// COLOR UTILITY FUNCTIONS // COLOR UTILITY FUNCTIONS
export function isSRgba8(value: unknown): value is SRGBA8 { export function isSRgba8(value: unknown): value is SRGBA8 {
return typeof value === "object" && value !== null && "red" in value; return !!value && typeof value === "object" && "red" in value;
} }
// Parse a CSS color string into an `SRGBA8`. Uses a canvas to delegate parsing to the browser. // Parse a CSS color string into an `SRGBA8`. Uses a canvas to delegate parsing to the browser.
@@ -183,11 +183,11 @@ export function contrastingOutlineFactor(value: FillChoice<SRGBA8>, proximityCol
// GRADIENT UTILITY FUNCTIONS // GRADIENT UTILITY FUNCTIONS
export function isGradientStops(value: unknown): value is GradientStops<SRGBA8> { export function isGradientStops(value: unknown): value is GradientStops<SRGBA8> {
return typeof value === "object" && value !== null && "color" in value && Array.isArray(value.color); return !!value && typeof value === "object" && "color" in value && Array.isArray(value.color);
} }
export function isGradientRamp(value: unknown): value is GradientRamp<SRGBA8> { export function isGradientRamp(value: unknown): value is GradientRamp<SRGBA8> {
return typeof value === "object" && value !== null && "stops" in value && isGradientStops(value.stops); return !!value && typeof value === "object" && "stops" in value && isGradientStops(value.stops);
} }
// FILL CHOICE UTILITY FUNCTIONS // FILL CHOICE UTILITY FUNCTIONS
@@ -203,8 +203,7 @@ export function fillChoiceGradient(value: FillChoice<SRGBA8>): GradientStops<SRG
} }
export function parseFillChoice(value: unknown): FillChoice<SRGBA8> { export function parseFillChoice(value: unknown): FillChoice<SRGBA8> {
if (value === "None" || value === undefined || value === null) return "None"; if (value && typeof value === "object" && "Solid" in value && isSRgba8(value.Solid)) return { Solid: value.Solid };
if (typeof value === "object" && value !== null && "Solid" in value && isSRgba8(value.Solid)) return { Solid: value.Solid }; if (value && typeof value === "object" && "Gradient" in value && isGradientRamp(value.Gradient)) return { Gradient: value.Gradient };
if (typeof value === "object" && value !== null && "Gradient" in value && isGradientRamp(value.Gradient)) return { Gradient: value.Gradient };
return "None"; return "None";
} }

View File

@@ -353,7 +353,7 @@ function targetIsTextField(target: EventTarget | HTMLElement | undefined): boole
function potentiallyRestoreCanvasFocus(e: Event) { function potentiallyRestoreCanvasFocus(e: Event) {
const appElement = window.document.querySelector("[data-app-container]"); const appElement = window.document.querySelector("[data-app-container]");
const app = appElement instanceof HTMLElement ? appElement : null; const app = appElement instanceof HTMLElement ? appElement : undefined;
const newInCanvasArea = const newInCanvasArea =
(e.target instanceof Element && e.target.closest("[data-viewport], [data-viewport-container], [data-graph]")) instanceof Element && (e.target instanceof Element && e.target.closest("[data-viewport], [data-viewport-container], [data-graph]")) instanceof Element &&

View File

@@ -147,7 +147,7 @@ async function migrateToNewFormat() {
if (oldDocuments) { if (oldDocuments) {
Object.values(oldDocuments).forEach((value) => { Object.values(oldDocuments).forEach((value) => {
const oldEntry: unknown = value; const oldEntry: unknown = value;
if (typeof oldEntry !== "object" || oldEntry === null) return; if (!oldEntry || typeof oldEntry !== "object") return;
if (!("documentId" in oldEntry) || !("document" in oldEntry) || !("details" in oldEntry)) return; if (!("documentId" in oldEntry) || !("document" in oldEntry) || !("details" in oldEntry)) return;
// Extract the document ID, handling bigint, number, and string formats // Extract the document ID, handling bigint, number, and string formats
@@ -168,7 +168,7 @@ async function migrateToNewFormat() {
// Extract document details, handling camelCase from the old shipped format // Extract document details, handling camelCase from the old shipped format
const details: unknown = oldEntry.details; const details: unknown = oldEntry.details;
if (typeof details !== "object" || details === null) return; if (!details || typeof details !== "object") return;
let name = ""; let name = "";
if ("name" in details && typeof details.name === "string") name = details.name; if ("name" in details && typeof details.name === "string") name = details.name;
@@ -193,7 +193,7 @@ async function migrateToNewFormat() {
// TODO: Eventually remove this document upgrade code // TODO: Eventually remove this document upgrade code
function extractIsSavedFromUnknown(details: unknown): boolean { function extractIsSavedFromUnknown(details: unknown): boolean {
if (typeof details !== "object" || details === null) return false; if (!details || typeof details !== "object") return false;
// Old camelCase format // Old camelCase format
if ("isSaved" in details) return Boolean(details.isSaved); if ("isSaved" in details) return Boolean(details.isSaved);