@sap/cds-dk
Version:
Command line client and development toolkit for the SAP Cloud Application Programming Model
58 lines (52 loc) • 2.37 kB
JavaScript
const indentRegEx = new RegExp(/^(\s*)/)
const leadingSpacesRegEx = new RegExp(/^( +)/)
const leadingTabsRegEx = new RegExp(/^(\t+)/)
/**
* Greatest Common Divisor Euclidean algorithm
* @param {number[]} xs - list of number to find the gcd of.
* @returns {number} - greatest common divisor of all numbers in xs.
*/
function gcd (xs) {
const euclid = (a, b) => b === 0 ? a : euclid(b, a % b)
const [a, b, ...tail] = xs
return tail.length
? gcd([euclid(a, b)].concat(tail))
: euclid(a, b ?? 0) // coalesce in case xs consists of exactly one element
}
/**
* Detects indent used for a file.
* The algorithm inspects the indentation of every line to find whether tabs or spaces are more prevalent.
* Once that has been determined, it derives the spacing by calculating the gcd of all relevant lines.
* I.e. if there are lines indented with 2, 4, or 8 spaces, it will find ' ' as indent.
* If lines are indented using 1 or 2 tabs, '\t' will be returned.
* TODO: filter out outliers, i.e. if the user used three spaces in one line, should ' ' space be used for spacing? Or should that be ignored?
* @param {string[]} file - lines of a file.
* @returns {string | null} - the string used to space (e.g. 2 spaces, 4 spaces, 1 tab, etc.) or null if the file was empty.
*/
function detectIndent (file) {
if (file.length === 0) return null
if (file.length === 1) return indentRegEx.exec(file[0])[0]
const indents = file
.map(line => indentRegEx.exec(line)[0])
.filter(Boolean)
.reduce((bins, indent) => {
const type = leadingSpacesRegEx.test(indent)
? 'spaces'
: leadingTabsRegEx.test(indent)
? 'tabs'
: undefined
if (type) {
const length = indent.split('').length
const bin = bins[type]
bin.total++
bin.indents[length] = (bin.indents[length] ?? 0) + 1
}
return bins
}, { tabs: { indents: {}, total: 0, character: '\t' }, spaces: { indents: {}, total: 0, character: ' ' } })
const preferred = indents.tabs.total > indents.spaces.total ? indents.tabs : indents.spaces
const width = gcd(Object.keys(preferred.indents)) ?? 2
return preferred.character.repeat(width)
}
module.exports = {
detectIndent
}