UNPKG

import-sort

Version:

Sort ES2015 (aka ES6) imports programmatically.

223 lines (222 loc) 6.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const detectNewline = require("detect-newline"); const StyleAPI_1 = require("./style/StyleAPI"); function importSort(code, rawParser, rawStyle, file, options) { let style; const parser = typeof rawParser === "string" ? require(rawParser) : rawParser; if (typeof rawStyle === "string") { style = require(rawStyle); // eslint-disable-next-line if (style.default) { // eslint-disable-next-line style = style.default; } } else { style = rawStyle; } return sortImports(code, parser, style, file, options); } exports.default = importSort; function sortImports(code, parser, style, file, options) { // eslint-disable-next-line const items = addFallback(style, file, options || {})(StyleAPI_1.default); const buckets = items.map(() => []); const imports = parser.parseImports(code, { file, }); if (imports.length === 0) { return { code, changes: [] }; } const eol = detectNewline.graceful(code); const changes = []; // Fill buckets for (const imported of imports) { let sortedImport = imported; const index = items.findIndex(item => { // eslint-disable-next-line sortedImport = sortNamedMembers(imported, item.sortNamedMembers); return !!item.match && item.match(sortedImport); }); if (index !== -1) { buckets[index].push(sortedImport); } } // Sort buckets buckets.forEach((bucket, index) => { const { sort } = items[index]; if (!sort) { return; } if (!Array.isArray(sort)) { bucket.sort(sort); return; } const sorters = sort; if (sorters.length === 0) { return; } const multiSort = (first, second) => { let sorterIndex = 0; let comparison = 0; while (comparison === 0 && sorters[sorterIndex]) { comparison = sorters[sorterIndex](first, second); sorterIndex += 1; } return comparison; }; bucket.sort(multiSort); }); let importsCode = ""; // Track if we need to insert a separator let separator = false; buckets.forEach((bucket, index) => { if (bucket.length > 0 && separator) { importsCode += eol; separator = false; } bucket.forEach(imported => { // const sortedImport = sortNamedMembers(imported, items[index].sortNamedMembers); const importString = parser.formatImport(code, imported, eol); importsCode += importString + eol; }); // Add separator but only when at least one import was already added if (items[index].separator && importsCode !== "") { separator = true; } }); let sortedCode = code; // Remove imports imports .slice() .reverse() .forEach(imported => { let importEnd = imported.end; if (sortedCode.charAt(imported.end).match(/\s/)) { importEnd += 1; } changes.push({ start: imported.start, end: importEnd, code: "", note: "import-remove", }); sortedCode = sortedCode.slice(0, imported.start) + sortedCode.slice(importEnd, code.length); }); const { start } = imports[0]; // Split code at first original import let before = code.substring(0, start); let after = sortedCode.substring(start, sortedCode.length); const oldBeforeLength = before.length; const oldAfterLength = after.length; let beforeChange; let afterChange; // Collapse all whitespace into a single blank line before = before.replace(/\s+$/, match => { beforeChange = { start: start - match.length, end: start, code: eol + eol, note: "before-collapse", }; return eol + eol; }); // Collapse all whitespace into a single new line after = after.replace(/^\s+/, match => { afterChange = { start, end: start + match.length, code: eol, note: "after-collapse", }; return eol; }); // Remove all whitespace at the beginning of the code if (before.match(/^\s+$/)) { beforeChange = { start: start - oldBeforeLength, end: start, code: "", note: "before-trim", }; before = ""; } // Remove all whitespace at the end of the code if (after.match(/^\s+$/)) { afterChange = { start, end: start + oldAfterLength, code: "", note: "after-trim", }; after = ""; } if (afterChange) { changes.push(afterChange); } if (beforeChange) { changes.push(beforeChange); } const change = { start: before.length, end: before.length, code: importsCode, note: "imports", }; changes.push(change); if (code === before + importsCode + after) { return { code, changes: [] }; } return { code: before + importsCode + after, changes, }; } exports.sortImports = sortImports; function sortNamedMembers(imported, rawSort) { const sort = rawSort; if (!sort) { return imported; } if (!Array.isArray(sort)) { const singleSortedImport = Object.assign({}, imported); singleSortedImport.namedMembers = [...imported.namedMembers].sort(sort); return singleSortedImport; } const sorters = sort; if (sorters.length === 0) { return imported; } const multiSort = (first, second) => { let sorterIndex = 0; let comparison = 0; while (comparison === 0 && sorters[sorterIndex]) { comparison = sorters[sorterIndex](first, second); sorterIndex += 1; } return comparison; }; const sortedImport = Object.assign({}, imported); sortedImport.namedMembers = [...imported.namedMembers].sort(multiSort); return sortedImport; } function applyChanges(code, changes) { let changedCode = code; for (const change of changes) { changedCode = changedCode.slice(0, change.start) + change.code + changedCode.slice(change.end, changedCode.length); } return changedCode; } exports.applyChanges = applyChanges; function addFallback(style, file, options) { return styleApi => { const items = [{ separator: true }, { match: styleApi.always }]; return style(styleApi, file, options).concat(items); }; }