Look at this code:
const isLoading = true;
const hasError = false;
const canSubmit = false;
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?
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
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
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)
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?
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
// eslint.config.js
import sumerish from 'eslint-plugin-sumerish';
export default [sumerish.configs.recommended];
Links
- 🔌 VS Code extension → https://marketplace.visualstudio.com/items?itemName=sumerish.sumerish
- 📦 npm → https://www.npmjs.com/package/eslint-plugin-sumerish
- ⭐ GitHub → https://github.com/robis24/sumerish-vscode
- 🎮 Demo → https://robis24.github.io/sumerish-vscode/
- 📝 The full manifesto → How We Fixed English
You've been naming things with informal type hints your whole career.
Now there's a linter for it.
Top comments (0)