import-sort-parser-typescript
Version:
An import-sort parser based on the TypeScript parser.
217 lines (216 loc) • 7.92 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const typescript = require("typescript");
function parseImports(code) {
const host = {
fileExists: () => true,
readFile: () => "",
getSourceFile: () => {
return typescript.createSourceFile("", code, typescript.ScriptTarget.Latest, true);
},
getDefaultLibFileName: () => "lib.d.ts",
writeFile: () => null,
getCurrentDirectory: () => "",
getDirectories: () => [],
getCanonicalFileName: fileName => fileName,
useCaseSensitiveFileNames: () => true,
getNewLine: () => typescript.sys.newLine,
};
const program = typescript.createProgram(["foo.ts"], {
noResolve: true,
target: typescript.ScriptTarget.Latest,
experimentalDecorators: true,
experimentalAsyncFunctions: true,
}, host);
const sourceFile = program.getSourceFile("foo.ts");
if (!sourceFile) {
throw new Error("Source file not found. This should not happen.");
}
const imports = [];
typescript.forEachChild(sourceFile, node => {
switch (node.kind) {
case typescript.SyntaxKind.ImportDeclaration: {
imports.push(parseImportDeclaration(code, sourceFile, node));
break;
}
case typescript.SyntaxKind.ImportEqualsDeclaration: {
break;
}
default: {
break;
}
}
});
return imports;
}
exports.parseImports = parseImports;
function parseImportDeclaration(code, sourceFile, importDeclaration) {
const importStart = importDeclaration.pos + importDeclaration.getLeadingTriviaWidth();
const importEnd = importDeclaration.end;
let start = importStart;
let end = importEnd;
const leadingComments = getComments(sourceFile, importDeclaration, false);
const trailingComments = getComments(sourceFile, importDeclaration, true);
if (leadingComments) {
const comments = leadingComments;
let current = leadingComments.length - 1;
let previous;
while (comments[current] && comments[current].end + 1 === start) {
if (code
.substring(comments[current].pos, comments[current].end)
.startsWith("#!")) {
break;
}
previous = current;
start = comments[previous].pos;
current -= 1;
}
}
if (trailingComments) {
const comments = trailingComments;
let current = 0;
let previous;
while (comments[current] && comments[current].pos - 1 === end) {
// TODO: Why is this not needed?
// if (comments[current].loc.start.line !== node.loc.start.line) {
// break;
// }
previous = current;
({ end } = comments[previous]);
current += 1;
}
}
const type = "import";
const moduleName = importDeclaration.moduleSpecifier
.getText()
.replace(/["']/g, "");
const imported = {
start,
end,
importStart,
importEnd,
type,
moduleName,
namedMembers: [],
};
const { importClause } = importDeclaration;
if (importClause) {
if (importClause.name) {
imported.defaultMember = importClause.name.text;
}
const { namedBindings } = importClause;
if (namedBindings) {
if (namedBindings.kind === typescript.SyntaxKind.NamespaceImport) {
const namespaceImport = namedBindings;
imported.namespaceMember = namespaceImport.name.text;
}
if (namedBindings.kind === typescript.SyntaxKind.NamedImports) {
const namedImports = namedBindings;
for (const element of namedImports.elements) {
const alias = element.name.text;
let name = alias;
if (element.propertyName) {
name = element.propertyName.text;
}
imported.namedMembers.push({
name: fixMultipleUnderscore(name),
alias: fixMultipleUnderscore(alias),
});
}
}
}
}
return imported;
}
// This hack circumvents a bug (?) in the TypeScript parser where a named
// binding's name or alias that consists only of underscores contains an
// additional underscore. We just remove the superfluous underscore here.
//
// See https://github.com/renke/import-sort/issues/18 for more details.
function fixMultipleUnderscore(name) {
if (name.match(/^_{2,}$/)) {
return name.substring(1);
}
return name;
}
// Taken from https://github.com/fkling/astexplorer/blob/master/src/parsers/js/typescript.js#L68
function getComments(sourceFile, node, isTrailing) {
if (node.parent) {
const nodePos = isTrailing ? node.end : node.pos;
const parentPos = isTrailing ? node.parent.end : node.parent.pos;
if (node.parent.kind === typescript.SyntaxKind.SourceFile ||
nodePos !== parentPos) {
let comments;
if (isTrailing) {
comments = typescript.getTrailingCommentRanges(sourceFile.text, nodePos);
}
else {
comments = typescript.getLeadingCommentRanges(sourceFile.text, nodePos);
}
if (Array.isArray(comments)) {
return comments;
}
}
}
return undefined;
}
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 }, index) => {
const lastImport = index === namedMembers.length - 1;
const comma = !useTrailingComma && lastImport ? "" : ",";
if (name === alias) {
return `${prefix}${name}${comma}` + eol;
}
return `${prefix}${name} as ${alias}${comma}` + eol;
})
.join("") +
"}");
}
const space = useSpaces ? " " : "";
const comma = useTrailingComma ? "," : "";
return ("{" +
space +
namedMembers
.map(({ name, alias }) => {
if (name === alias) {
return `${name}`;
}
return `${name} as ${alias}`;
})
.join(", ") +
comma +
space +
"}");
}