UNPKG

import-sort-parser-babylon

Version:

An import-sort parser based on the JavaScript parser Babylon.

205 lines (204 loc) 7.88 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const path_1 = require("path"); const core_1 = require("@babel/core"); const parser_1 = require("@babel/parser"); const traverse_1 = require("@babel/traverse"); const types_1 = require("@babel/types"); // TODO: Mocha currently doesn't pick up the declaration in index.d.ts // eslint-disable-next-line const findLineColumn = require("find-line-column"); const TYPESCRIPT_EXTENSIONS = [".ts", ".tsx"]; const COMMON_PARSER_PLUGINS = [ "jsx", "doExpressions", "objectRestSpread", ["decorators", { decoratorsBeforeExport: true }], "classProperties", "classPrivateProperties", "classPrivateMethods", "exportDefaultFrom", "exportNamespaceFrom", "asyncGenerators", "functionBind", "functionSent", "dynamicImport", "numericSeparator", "optionalChaining", "importMeta", "bigInt", "optionalCatchBinding", "throwExpressions", ["pipelineOperator", { proposal: "minimal" }], "nullishCoalescingOperator", ]; const FLOW_PARSER_PLUGINS = ["flow", "flowComments", ...COMMON_PARSER_PLUGINS]; const FLOW_PARSER_OPTIONS = { allowImportExportEverywhere: true, allowAwaitOutsideFunction: true, allowReturnOutsideFunction: true, allowSuperOutsideMethod: true, sourceType: "module", plugins: FLOW_PARSER_PLUGINS, }; const TYPESCRIPT_PARSER_PLUGINS = ["typescript", ...COMMON_PARSER_PLUGINS]; const TYPESCRIPT_PARSER_OPTIONS = { allowImportExportEverywhere: true, allowAwaitOutsideFunction: true, allowReturnOutsideFunction: true, allowSuperOutsideMethod: true, sourceType: "module", plugins: TYPESCRIPT_PARSER_PLUGINS, }; function parseImports(code, options = {}) { const babelPartialOptions = core_1.loadPartialConfig({ filename: options.file }); let parsed; if (babelPartialOptions.hasFilesystemConfig()) { // We always prefer .babelrc (or similar) if one was found parsed = core_1.parse(code, core_1.loadOptions({ filename: options.file })); } else { const { file } = options; const isTypeScript = file && TYPESCRIPT_EXTENSIONS.includes(path_1.extname(file)); const parserOptions = isTypeScript ? TYPESCRIPT_PARSER_OPTIONS : FLOW_PARSER_OPTIONS; parsed = parser_1.parse(code, parserOptions); } const imports = []; const ignore = (parsed.comments || []).some(comment => { return comment.value.includes("import-sort-ignore"); }); if (ignore) { return imports; } traverse_1.default(parsed, { ImportDeclaration(path) { const { node } = path; const importStart = node.start; const importEnd = node.end; let start = importStart; let end = importEnd; if (node.leadingComments) { const comments = node.leadingComments; let current = node.leadingComments.length - 1; let previous; while (comments[current] && comments[current].end + 1 === start) { if (code .substring(comments[current].start, comments[current].end) .indexOf("#!") === 0) { break; } // TODO: Improve this so that comments with leading whitespace are allowed if (findLineColumn(code, comments[current].start).col !== 0) { break; } previous = current; ({ start } = comments[previous]); current -= 1; } } if (node.trailingComments) { const comments = node.trailingComments; let current = 0; let previous; while (comments[current] && comments[current].start - 1 === end) { if (comments[current].loc.start.line !== node.loc.start.line) { break; } previous = current; ({ end } = comments[previous]); current += 1; } } const imported = { start, end, importStart, importEnd, moduleName: node.source.value, type: node.importKind === "type" ? "import-type" : "import", namedMembers: [], }; if (node.specifiers) { node.specifiers.forEach(specifier => { if (types_1.isImportSpecifier(specifier)) { const type = specifier.importKind === "type" ? { type: true } : {}; imported.namedMembers.push(Object.assign({ name: specifier.imported.name, alias: specifier.local.name }, type)); } else if (types_1.isImportDefaultSpecifier(specifier)) { imported.defaultMember = specifier.local.name; } else if (types_1.isImportNamespaceSpecifier) { imported.namespaceMember = specifier.local.name; } }); } imports.push(imported); }, }); return imports; } exports.parseImports = parseImports; function formatImport(code, imported, eol = "\n") { const importStart = imported.importStart || imported.start; const importEnd = imported.importEnd || imported.end; const importCode = code.substring(importStart, importEnd); const { namedMembers } = imported; if (namedMembers.length === 0) { return code.substring(imported.start, imported.end); } const newImportCode = importCode.replace(/\{[\s\S]*\}/g, namedMembersString => { const useMultipleLines = namedMembersString.indexOf(eol) !== -1; let prefix; if (useMultipleLines) { [prefix] = namedMembersString .split(eol)[1] .match(/^\s*/); } const useSpaces = namedMembersString.charAt(1) === " "; const userTrailingComma = namedMembersString .replace("}", "") .trim() .endsWith(","); return formatNamedMembers(namedMembers, useMultipleLines, useSpaces, userTrailingComma, prefix, eol); }); return (code.substring(imported.start, importStart) + newImportCode + code.substring(importEnd, importEnd + (imported.end - importEnd))); } exports.formatImport = formatImport; function formatNamedMembers(namedMembers, useMultipleLines, useSpaces, useTrailingComma, prefix, eol = "\n") { if (useMultipleLines) { return ("{" + eol + namedMembers .map(({ name, alias, type }, index) => { const lastImport = index === namedMembers.length - 1; const comma = !useTrailingComma && lastImport ? "" : ","; const typeModifier = type ? "type " : ""; if (name === alias) { return `${prefix}${typeModifier}${name}${comma}` + eol; } return `${prefix}${typeModifier}${name} as ${alias}${comma}` + eol; }) .join("") + "}"); } const space = useSpaces ? " " : ""; const comma = useTrailingComma ? "," : ""; return ("{" + space + namedMembers .map(({ name, alias, type }) => { const typeModifier = type ? "type " : ""; if (name === alias) { return `${typeModifier}${name}`; } return `${typeModifier}${name} as ${alias}`; }) .join(", ") + comma + space + "}"); }