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

prevOpen Source→string-dashesnext

string-dashes1.4.6

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

No 3rd party dependencies. All dependencies and devDependencies, checked recursively, are Codsen packages.

Permalink to InstallationInstallation

Permalink to Quick TakeQuick Take

Permalink to ExamplesExamples

  • 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. A hyphen or en dash becomes —; an existing prose em dash stays unchanged. The exception is arithmetic: numeric or currency operands (1 - 2, $5 - $2, 5$ - 2$) receive an en dash for a hyphen, en dash, or em dash. This includes spaced em dashes such as 1 — 2 and any amount of whitespace.
  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
convertDashesBooleantruenoSet to false to leave dashes unchanged.

Omitted or undefined conversion flags use their defaults. An explicit convertDashes: false disables conversion.

Use convertOne() to provide your own span or decoded symbol. convertAll() scans literal characters; it does not decode HTML entities automatically.

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 indexundefinedyesInclusive UTF-16 index at which the supplied span starts.
to
Type: Natural number, string index
Default: from + 1
Obligatory: no
toNatural number, string indexfrom + 1noExclusive UTF-16 end index. An integer endpoint must satisfy from ≤ to ≤ str.length.
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
convertDashesBooleantruenoSet to false to leave dashes unchanged.
offsetBy
Type: Function
Default: undefined
Obligatory: no
offsetByFunctionundefinednoAccepted for API compatibility; the current dash rules do not invoke this callback.

Pass an options object; null receives the required-options error. opts.from must be an integer from 0 to str.length - 1. opts.to defaults to from + 1. Backwards or out-of-bounds integer spans throw an error prefixed with string-dashes/convertOne().

Equal endpoints are valid: a supplied value can insert a dash without deleting source text. For example, convertOne("12", { from: 1, to: 1, value: "-" }) returns [[1, 1, "–"]].

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, "—"]]

The context scan begins after the complete supplied span, including when multiple spaces, tabs, or newlines separate it from the next operand:

import { convertOne } from "string-dashes";

convertOne("1 -  2", { from: 2, to: 7, value: "-" });
// => [[2, 7, "–"]]

That’s how detergent uses this package. convertEntities controls the encoding of replacements; it does not encode already-correct prose em dashes.

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";

Permalink to changelogChangelog

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