Skip to Content
  • Website
Codsen
  • Home
  • Open Source
  • Articles
  • About

prevOpen Source→string-dashesnext

string-dashes1.4.1

Comprehensive, HTML-entities-aware tool to typographically-correct the dashes and hyphens

Downloads per monthChangelogMIT Licenselibera manifesto
  • the top
  • Installation
  • Quick Take
  • Examples
  • IDEA
  • THE RULES
  • API — CONVERTALL()
  • API — CONVERTONE()
  • OPTS — VALUE
  • API — DEFAULTS
  • API — VERSION
  • API — TYPES
  • Changelog

Installation

Quick Take

Examples

  • An ambiguous hyphen is left alone
  • Convert one em dash into an entity
  • Disable the dash conversion
  • Convert a letter range into an en dash
  • Convert a number range into an en dash
  • Convert a maths minus into an en dash
  • Report the index offsets through a callback
  • Supply the dash value separately

Idea

A hyphen (-) is not a dash. Typographically-correct English text uses three different characters:

CharacterNameUsed for
-
-hyphencompound words — well-known
–opens in a new tab (–)
–opens in a new tab (–)en dashranges and minus — 1880–1912, 5 – 2 = 3
—opens in a new tab (—)
—opens in a new tab (—)em dasha break in a sentence — like this one

This program finds hyphens and dashes which are in the wrong place and reports what to replace them with. It reads the surrounding characters to decide, so it does not need you to mark anything up.

This is a sibling of string-apostrophes; both are used by detergent.

Sources used in rules logic and unit tests:

  • Oxford A–Z of Grammar and Punctuation 2nd Ed., 2009, ISBN 978–0199564675opens in a new tab
  • Butterick’s Practical Typography 2nd Ed., “Hyphens and dashes” chapteropens in a new tab

The rules

The program applies three rules. In each, the character being evaluated is a hyphen, an en dash or an em dash.

  1. Number range or A-Z range → en dash. A hyphen with a digit on each side (1880-1912) becomes an en dash. So does a hyphen between two standalone uppercase letters (A-Z).
  2. Whitespace on both sides → em dash. - becomes —. The exception is arithmetic: if the nearest non-whitespace characters on both sides are digits or currency symbols (1 - 2, $5 - $2), the hyphen is a minus sign, so it becomes an en dash instead.
  3. Cut-off speech → em dash. A letter, then a hyphen, then a quote ("I was just abo-") becomes an em dash.

Anything the rules don’t recognise is left alone.

API — convertAll()

The main function convertAll() is imported like this:

It’s a function which takes two input arguments:

Input argumentTypeObligatoryDescription
str
Type: String
Obligatory: yes
strStringyesString to process.
opts
Type: Plain object
Obligatory: no
optsPlain objectnoOptional Options Object.

The Optional Options Object has the following shape:

KeyTypeDefaultObligatoryDescription
convertEntities
Type: Boolean
Default: false
Obligatory: no
convertEntitiesBooleanfalsenoSet to true to insert –/— instead of the raw characters.
convertDashes
Type: Boolean
Default: true
Obligatory: no
convertDashesBooleantruenoA killswitch. If it’s false, the program does nothing.

convertAll() also accepts from, to, value and offsetBy, but it sets from itself while looping, so there’s little point passing them. Use convertOne() when you want to drive the indexes yourself.

Here are all defaults in one place for copying:

The function will return a plain object:

Returned object’s keyTypeDescription
result
Type: String
resultStringProcessed string, with all ranges applied
ranges
Type: Array of arrays, or null
rangesArray of arrays, or nullRanges that were gathered and applied to produce the result. It’s an empty array when nothing needed fixing, and null when str was empty.

For example:

import { convertAll } from "string-dashes";

console.log(
  convertAll("Dashes come in two sizes - the en dash and the em dash.", {
    convertEntities: true,
  }),
);
// => {
//      result: "Dashes come in two sizes — the en dash and the em dash.",
//      ranges: [[25, 26, "—"]],
//    }

API — convertOne()

The main function convertOne() is imported like this:

It’s a function which takes two input arguments:

Input argumentTypeObligatoryDescription
str
Type: String
Obligatory: yes
strStringyesA string to process
opts
Type: Plain object
Obligatory: yes
optsPlain objectyesObligatory Options Object.

The Obligatory Options Object has the following shape:

KeyTypeDefaultObligatoryDescription
from
Type: Natural number, string index
Default: undefined
Obligatory: yes
fromNatural number, string indexundefinedyesIndex at which the character to process starts.
to
Type: Natural number, string index
Default: from + 1
Obligatory: no
toNatural number, string indexfrom + 1noIndex at which the character to process ends.
value
Type: String
Default: undefined
Obligatory: no
valueStringundefinednoOverride the value present at str.slice(from, to). Recognised values are -, – and —.
convertEntities
Type: Boolean
Default: false
Obligatory: no
convertEntitiesBooleanfalsenoSet to true to insert –/— instead of the raw characters.
convertDashes
Type: Boolean
Default: true
Obligatory: no
convertDashesBooleantruenoKillswitch. If it’s false, the program does nothing.
offsetBy
Type: Function
Default: undefined
Obligatory: no
offsetByFunctionundefinednoIf you provide a function, it will be called with a natural number input argument, meaning how much characters to skip next.

opts.from is obligatory — that’s how you tell the program which character to process. It throws if from is missing, negative, or beyond the end of str.

The function returns ranges: an array of range arrays or null. Later you can use them in ranges-apply to process a string using those ranges (in other words, “to apply those ranges”).

import { convertOne } from "string-dashes";

console.log(
  convertOne("Dashes come in two sizes - the en dash and the em dash.", {
    from: 25,
    convertEntities: true,
  }),
);
// => [[25, 26, "—"]]

opts.value

Use opts.value when the character sits in the string encoded, or represented by something other than a literal -, – or —. You tell the program where the “symbol” starts (from) and ends (to), and what it stands for (value); the program then evaluates the surroundings as if a real dash were there:

import { convertOne } from "string-dashes";

console.log(
  convertOne("Dashes come in two sizes - the en dash.", {
    from: 25,
    to: 30,
    value: "-", // tell the program indexes 25-30 represent a hyphen
  }),
);
// => [[25, 30, "—"]]

That’s how detergent uses this package.

API — defaults

You can import defaults:

It's a plain object:

The main function calculates the options to be used by merging the options you passed with these defaults.

These defaults are applicable for both convertOne() and convertAll() functions.

API — version

You can import version:

API — types

This package is written in TypeScript and exports the following types:

TypeDescription
Opts
Type: Opts
OptsThe Options Object of both convertAll() and convertOne(), shown above.
Range
Type: Range
RangeA single range, re-exported from ranges-apply.
Ranges
Type: Ranges
RangesZero or more Ranges, or null, re-exported from ranges-apply.
import type { Opts, Range, Ranges } from "string-dashes";

Changelog

Open Changelog
↑ back to top
prev next

Copyright

All rights reserved © Roy Revelt 2026
All our open source packages are under MIT licenceopens in a new tab

Activities

🐛 See a bug? Raise an issueopens in a new tab
💘 Check out the Indiewebopens in a new tab and Libera manifestoopens in a new tab