DEV Community

Cover image for You've been writing Sumerish for years. You just didn't know it had a name.
Robis Koopmans
Robis Koopmans

Posted on

You've been writing Sumerish for years. You just didn't know it had a name.

Look at this code:

const isLoading = true;
const hasError = false;
const canSubmit = false;
Enter fullscreen mode Exit fullscreen mode

You already know what those variables contain without reading the rest of the file.
is → boolean. has → boolean. can → boolean.

You've been using suffixes to communicate type intent your entire career.
We all have. Informally. Inconsistently. Unenforced.


The problem

Now look at this:

const isLoading = [];        // wait, what?
const hasPermissions = () => fetch('/api/permissions'); // that's a Promise
const canSubmit = 'yes';     // seriously?
Enter fullscreen mode Exit fullscreen mode

The name promises one thing. The value delivers another.
Your linter doesn't care. Your tests might not catch it until runtime.
Your colleague who reads this at 11pm definitely suffers.

There's a classic joke in programming:

"There are only two hard things in computer science: cache invalidation and naming things."

We laugh because it's true. But the real problem isn't naming things.
It's that we name things with no contract. The name is a suggestion, not a commitment.


What if the suffix was the contract?

This is the core idea behind Sumerish — a naming protocol where the suffix chain encodes a type commitment that your linter can actually enforce.

const loginIs = true;           // ✅ -is → boolean
const userEn = [];              // ✅ -en → Array
const fetchWill = async () => {}; // ✅ -will → Promise
function processDo() {}         // ✅ -do → void function
function getUserQ() { return u; } // ✅ -q → function with return value
Enter fullscreen mode Exit fullscreen mode

Violate the contract:

const loginIs = [];              // ⚠ -is implies boolean, assigned array
const fetchWill = () => {};      // ⚠ -will implies Promise, function is not async
const processDo = () => result;  // ⚠ -do implies void, use -q for queries
function getUserQ() {}           // ⚠ -q implies return value, function has no return
Enter fullscreen mode Exit fullscreen mode

The linter catches it. Before your tests do.


The suffix map

Suffix Implied type Slot
-is / -no boolean 4
-en Array 1
-will Promise 4
-do () => void 2
-q () => T 5
-me owned/my 1
-in location 3

The slot is the key — suffixes must appear in precedence order.
The linter enforces that too:

const userIsEn = [];   // ⚠ -is (slot 4) before -en (slot 1)
Enter fullscreen mode Exit fullscreen mode

How is this different from @typescript-eslint/naming-convention?

@typescript-eslint/naming-convention enforces shape — camelCase, PascalCase, leading underscores.

It can tell you loginIs is valid camelCase.
It cannot tell you loginIs should be a boolean.

Sumerish enforces semantics. The suffix is not decoration. It is a type contract.


The reading curve is nearly zero

This is the part that surprised me most when building this.

You don't need to learn Sumerish to read it.

Report-view-pl-q?
Enter fullscreen mode Exit fullscreen mode

You just understood that. "Could you please look at the report?"

The roots stay English. The hyphens make the structure visible.
The chain is self-documenting.

You've already been using is, has, can as informal suffixes.
Sumerish just makes it systematic, consistent, and linted.


Install

npm install --save-dev eslint-plugin-sumerish
Enter fullscreen mode Exit fullscreen mode
// eslint.config.js
import sumerish from 'eslint-plugin-sumerish';
export default [sumerish.configs.recommended];
Enter fullscreen mode Exit fullscreen mode

Links


You've been naming things with informal type hints your whole career.
Now there's a linter for it.

Top comments (0)