-
Notifications
You must be signed in to change notification settings - Fork 96
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Sort out whitespace between text and HTML nodes (#120)
This solves the issue of whitespace either being wrongly added or removed inside inline tags or between text and tags. Fixes #58, fixes #103, fixes #24
- Loading branch information
1 parent
b901925
commit bc0e0c7
Showing
19 changed files
with
522 additions
and
178 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
export type TagName = keyof HTMLElementTagNameMap | 'svg'; | ||
|
||
// @see http://xahlee.info/js/html5_non-closing_tag.html | ||
export const selfClosingTags = [ | ||
'area', | ||
'base', | ||
'br', | ||
'col', | ||
'embed', | ||
'hr', | ||
'img', | ||
'input', | ||
'link', | ||
'meta', | ||
'param', | ||
'source', | ||
'track', | ||
'wbr', | ||
]; | ||
|
||
// https://developer.mozilla.org/en-US/docs/Web/HTML/Inline_elements#Elements | ||
export const inlineElements: TagName[] = [ | ||
'a', | ||
'abbr', | ||
'audio', | ||
'b', | ||
'bdi', | ||
'bdo', | ||
'br', | ||
'button', | ||
'canvas', | ||
'cite', | ||
'code', | ||
'data', | ||
'datalist', | ||
'del', | ||
'dfn', | ||
'em', | ||
'embed', | ||
'i', | ||
'iframe', | ||
'img', | ||
'input', | ||
'ins', | ||
'kbd', | ||
'label', | ||
'map', | ||
'mark', | ||
'meter', | ||
'noscript', | ||
'object', | ||
'output', | ||
'picture', | ||
'progress', | ||
'q', | ||
'ruby', | ||
's', | ||
'samp', | ||
'select', | ||
'slot', | ||
'small', | ||
'span', | ||
'strong', | ||
'sub', | ||
'sup', | ||
'svg', | ||
'template', | ||
'textarea', | ||
'time', | ||
'u', | ||
'var', | ||
'video', | ||
'wbr' | ||
]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
import { Doc, doc } from 'prettier'; | ||
|
||
export function isLine(doc: Doc) { | ||
return typeof doc === 'object' && doc.type === 'line' | ||
} | ||
|
||
export function isLineDiscardedIfLonely(doc: Doc) { | ||
return isLine(doc) && !(doc as doc.builders.Line).keepIfLonely | ||
} | ||
|
||
/** | ||
* Check if the doc is empty, i.e. consists of nothing more than empty strings (possibly nested). | ||
*/ | ||
export function isEmptyDoc(doc: Doc): boolean { | ||
if (typeof doc === 'string') { | ||
return doc.length === 0; | ||
} | ||
|
||
if (doc.type === 'line') { | ||
return !doc.keepIfLonely; | ||
} | ||
|
||
const { contents } = doc as { contents?: Doc }; | ||
|
||
if (contents) { | ||
return isEmptyDoc(contents); | ||
} | ||
|
||
const { parts } = doc as { parts?: Doc[] }; | ||
|
||
if (parts) { | ||
return isEmptyGroup(parts); | ||
} | ||
|
||
return false; | ||
} | ||
|
||
export function isEmptyGroup(group: Doc[]): boolean { | ||
return !group.find(doc => !isEmptyDoc(doc)) | ||
} | ||
|
||
/** | ||
* Trims both leading and trailing nodes matching `isWhitespace` independent of nesting level | ||
* (though all trimmed adjacent nodes need to be a the same level). Modifies the `docs` array. | ||
*/ | ||
export function trim(docs: Doc[], isWhitespace: (doc: Doc) => boolean): Doc[] { | ||
trimLeft(docs, isWhitespace); | ||
trimRight(docs, isWhitespace); | ||
|
||
return docs; | ||
} | ||
|
||
/** | ||
* Trims the leading nodes matching `isWhitespace` independent of nesting level (though all nodes need to be a the same level) | ||
* and returnes the removed nodes. | ||
*/ | ||
export function trimLeft(group: Doc[], isWhitespace: (doc: Doc) => boolean): Doc[] | undefined { | ||
let firstNonWhitespace = group.findIndex((doc) => !isWhitespace(doc)); | ||
|
||
if (firstNonWhitespace < 0 && group.length) { | ||
firstNonWhitespace = group.length; | ||
} | ||
|
||
if (firstNonWhitespace > 0) { | ||
return group.splice(0, firstNonWhitespace); | ||
} else { | ||
const parts = getParts(group[0]); | ||
|
||
if (parts) { | ||
return trimLeft(parts, isWhitespace); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Trims the trailing nodes matching `isWhitespace` independent of nesting level (though all nodes need to be a the same level) | ||
* and returnes the removed nodes. | ||
*/ | ||
export function trimRight(group: Doc[], isWhitespace: (doc: Doc) => boolean): Doc[] | undefined { | ||
let lastNonWhitespace = group.length ? findLastIndex((doc) => !isWhitespace(doc), group) : 0; | ||
|
||
if (lastNonWhitespace < group.length - 1) { | ||
return group.splice(lastNonWhitespace + 1); | ||
} else { | ||
const parts = getParts(group[group.length - 1]); | ||
|
||
if (parts) { | ||
return trimRight(parts, isWhitespace); | ||
} | ||
} | ||
} | ||
|
||
function getParts(doc: Doc): Doc[] | undefined { | ||
if (typeof doc === 'object' && (doc.type === 'fill' || doc.type === 'concat')) { | ||
return doc.parts; | ||
} | ||
} | ||
|
||
function findLastIndex<T>(isMatch: (item: T) => boolean, items: T[]) { | ||
for (let i = items.length - 1; i >= 0; i--) { | ||
if (isMatch(items[i])) { | ||
return i; | ||
} | ||
} | ||
|
||
return -1; | ||
} |
Oops, something went wrong.