npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

sm-no-saccade-style

v0.1.1

Published

ESLint style plugin for left-rail punctuation, Allman blocks, tabs, and tight control parens.

Downloads

1,360

Readme

sm-no-saccade-style

npm version npm license CI Codecov

Why saccades matter

When reading code, the eye does not move smoothly across the page. It jumps in quick motions called saccades, then pauses briefly to take in detail.

Those jumps are cheap when the next meaningful symbol is easy to predict and easy to find. They become expensive when punctuation that controls structure is scattered across the far right edge of lines, because the eye has to keep leaving the left rail to confirm where lists continue, where expressions break, and where blocks open or close.

That repeated scanning adds friction. It slows reacquisition after every line break, increases miss risk in dense multiline code, and makes editing harder because structural markers are not where the eye naturally returns.

sm-no-saccade-style is built to reduce that cost. It pulls high-signal syntax back toward the left side of the line so continuation structure is visible earlier, with less hunting and fewer long corrective eye movements.

Design

Put control symbols where the eye naturally reacquires the next line.

This style favors:

  • leading commas
  • no trailing commas
  • leading boolean / arithmetic continuation operators
  • leading ternary markers
  • Allman braces for declarations and block forms
  • same-line braces for inline functions, inline class expressions, and inline object methods
  • no extra blank lines between adjacent delimiter-only lines
  • at most one blank line between a closing delimiter line and the next opening delimiter line
  • tab indentation
  • tight control parentheses (if(x))
  • no trailing whitespace outside strings
  • final trailing newline

Example

Conventional

const visibleMaps = maps.filter(map => map.visible && map.ready || map === activeMap).map(map => ({
	key: map.id,
	label: map.name,
	bounds: [map.x + camera.x, map.y + camera.y, map.width + marginX, map.height + marginY],
	layers: map.layers.filter(layer => layer.visible && layer.depth > minDepth).map(layer => ({
		name: layer.name,
		opacity: layer.opacity,
		tiles: layer.tiles.filter(tile => tile.index !== 0 && tile.visible).map(tile => ({
			id: tile.id,
			src: tile.src,
			position: [tile.x + offsetX, tile.y + offsetY]
		}))
	}))
}));

if (motionParent
	&& !world.motionGraph.getParent(motionParent)
	&& !maps.has(motionParent)
	&& (state.changed || queue.length && !paused))
{
	world.motionGraph.delete(this);
}

sm-no-saccade-style

const visibleMaps = maps
	.filter(map =>
		map.visible
		&& map.ready
		|| map === activeMap
	)
	.map(map => ({
		key: map.id
		, label: map.name
		, bounds: [
			map.x + camera.x
			, map.y + camera.y
			, map.width + marginX
			, map.height + marginY
		]
		, layers: map.layers
			.filter(layer =>
				layer.visible
				&& layer.depth > minDepth
			)
			.map(layer => ({
				name: layer.name
				, opacity: layer.opacity
				, tiles: layer.tiles
					.filter(tile =>
						tile.index !== 0
						&& tile.visible
					)
					.map(tile => ({
						id: tile.id
						, src: tile.src
						, position: [
							tile.x + offsetX
							, tile.y + offsetY
						]
					}))
			}))
	}));

if(motionParent
	&& !world.motionGraph.getParent(motionParent)
	&& !maps.has(motionParent)
	&& (
		state.changed
		|| queue.length
		&& !paused
	)
){
	world.motionGraph.delete(this);
}

Usage

import smNoSaccadeStyle from 'sm-no-saccade-style';

export default [
	...smNoSaccadeStyle.configs.recommended,
];

The recommended config works for both JavaScript and TypeScript.

Style Guide

This section describes how to lay out code so it already matches the formatter and lint rules this package enforces.

Lists and records

Keep commas on the next line for multiline arrays, objects, object patterns, and array patterns.

const point = {
	x: 10
	, y: 20
	, label: 'spawn'
};

const bounds = [
	left
	, top
	, right
	, bottom
];

const {
	id
	, name
	, enabled
} = config;

Put exactly one space after a leading comma.

const list = [
	a
	, b
	, c
];

Short grouped rows are allowed when they stay compact.

const uv = [
	0.0, 0.0
	, 1.0, 0.0
	, 0.0, 1.0
];

When a grouped row gets too wide, split it so each item gets its own line.

const values = [
	reallyLongIdentifierAlpha
	, reallyLongIdentifierBeta
	, reallyLongIdentifierGamma
];

Do not use trailing commas in the recommended preset.

const user = {
	id: 1
	, name: 'Ada'
};

Continuation operators

Put multiline continuation operators at the beginning of the continued line.

const ready = cache.loaded
	&& !queue.length
	&& currentUser
	&& currentUser.enabled;

const total = subtotal
	+ shipping
	- discount;

Ternary markers also lead the continued line.

const label = isReady
	? 'ready'
	: 'pending';

Braces and heads

Use Allman braces for declarations and normal block forms.

function refresh()
{
	render();
}

if(visible)
{
	render();
}

switch(mode)
{
	case 'edit':
		return edit();
}

Keep the opening brace on the same line when the body is inline by design:

  • arrow functions with block bodies
  • function expressions
  • inline class expressions
  • inline object methods
promise.then(result => {
	handle(result);
});

const loader = function() {
	return cache.read();
};

const Widget = class {
	render()
	{
		return output;
	}
};

const api = {
	load() {
		return fetchData();
	}
};

Multiline control heads keep { on the same line as the closing ).

if(primaryTarget
	&& !visited.has(primaryTarget)
	&& !queue.length
){
	visit(primaryTarget);
}

Multiline function heads also keep { on the same line as the closing ).

class Scene
{
	constructor({
		width
		, height
	}){
		this.width = width;
		this.height = height;
	}
}

Empty inline bodies are allowed for compact declarations.

class NullLogger
{
	info(){}
}

if(disabled){}

Delimiter rails

Do not add extra blank lines between adjacent delimiter-only lines.

const config = {
	range: [
		start
		, end
	]
};

Do not stack closing delimiters from different indentation rails on the same line when they belong to different openings.

const loadSlices = layers.map(
	layer => layer.tiles.map(tile => ({
		id: tile.id
	}))
);

Indentation and whitespace

Indent with tabs. Use alignment only where smart tabs make the structure clearer.

const shortName = 1;
const longerKey = 2;

Use tight control parens.

if(ready)
{
	run();
}

while(queue.length)
{
	drain(queue);
}

Avoid trailing spaces and keep one final newline at the end of each file.

Rules

  • sm-no-saccade-style/leading-comma-lists Moves commas to the left rail for multiline arrays and objects.
  • sm-no-saccade-style/final-comma-line Forbids trailing commas in the recommended preset. The rule itself still supports allow, require, and forbid.
  • sm-no-saccade-style/leading-operators Moves continuation operators to the beginning of the next line.
  • sm-no-saccade-style/allman-tabs Enforces Allman braces for declarations and block forms, but keeps inline functions, inline class expressions, and inline object methods on the same line.
  • sm-no-saccade-style/no-double-closing-gap Forbids extra blank lines between delimiter-only lines: no blank lines for open -> open or close -> close, and at most one blank line for close -> open.
  • sm-no-saccade-style/no-space-control-paren Enforces tight control parens like if(x).
  • sm-no-saccade-style/no-trailing-whitespace Strips trailing spaces and tabs unless they are part of template-string content.
  • @stylistic/eol-last Requires one final trailing newline at end of file.