sortier
Version:
An opinionated code sorter
173 lines (172 loc) • 8.13 kB
JavaScript
// Parsers
import { parse as parseFlow } from "../parsers/flow/index.js";
import { parse as parseTypescript } from "../parsers/typescript/index.js";
// Types of sorts
import { sortContents } from "../sortContents/index.js";
import { sortImportDeclarationSpecifiers, } from "../sortImportDeclarationSpecifiers/index.js";
import { sortImportDeclarations } from "../sortImportDeclarations/index.js";
import { sortJsxElement } from "../sortJsxElement/index.js";
import { sortObjectTypeAnnotation } from "../sortObjectTypeAnnotation/index.js";
import { sortSwitchCases } from "../sortSwitchCases/index.js";
import { sortTSPropertySignatures } from "../sortTSPropertySignatures/index.js";
import { sortUnionTypeAnnotation } from "../sortUnionTypeAnnotation/index.js";
import { ArrayUtils } from "../../utilities/array-utils.js";
import { LogUtils, LoggerVerboseOption } from "../../utilities/log-utils.js";
import { isIgnored } from "../../utilities/sort-utils.js";
import { StringUtils } from "../../utilities/string-utils.js";
export class Reprinter {
static EXTENSIONS = [".cjs", ".js", ".js.txt", ".jsx", ".mjs", ".ts", ".tsx", ".ts.txt"];
// @ts-expect-error: Need to move to a functional system
_filename;
// @ts-expect-error: Need to move to a functional system
_helpModeHasPrintedFilename;
// @ts-expect-error: Need to move to a functional system
_options;
getRewrittenContents(filename, fileContents, options) {
this._filename = filename;
this._options = this.getValidatedOptions(options);
this._helpModeHasPrintedFilename = false;
const parser = this.getParser();
const ast = parser(fileContents);
const comments = ast.comments;
return this.rewriteNodes([ast], comments, fileContents);
}
isFileSupported(filename) {
return StringUtils.stringEndsWithAny(filename, [...Reprinter.EXTENSIONS]);
}
getValidatedOptions(appOptions) {
const partialOptions = appOptions.js || {};
let sortTypeAnnotations = undefined;
if (partialOptions.sortTypeAnnotations != null) {
sortTypeAnnotations = partialOptions.sortTypeAnnotations.slice();
ArrayUtils.dedupe(sortTypeAnnotations);
}
return {
...partialOptions,
sortTypeAnnotations,
};
}
getParser() {
// If the options overide the parser type
if (this._options.parser === "flow") {
return parseFlow;
}
// Make sure typescript can handle the extension
const isSupportedFileExtension = StringUtils.stringEndsWithAny(this._filename, Reprinter.EXTENSIONS);
if (isSupportedFileExtension) {
return parseTypescript;
}
throw new Error("File not supported");
}
rewriteNodes(originalNodes, comments, fileContents) {
let lastClassName = undefined;
const nodes = originalNodes.slice();
while (nodes.length !== 0) {
const node = nodes.shift();
if (Array.isArray(node)) {
throw new Error("Unexpected Exception - Array sent as node in rewrite nodes");
}
if (node == null) {
throw new Error("Unexpected Exception - Node received is null");
}
if (isIgnored(fileContents, comments, node)) {
continue;
}
// Now go through and push any bodies in the current context to the stack
try {
const nodeValues = Object.values(node);
for (const value of nodeValues) {
const unFilteredPropertyArray = Array.isArray(value) ? value : [value];
const actionablePropertyArray = unFilteredPropertyArray.filter((value) => {
return value != null && value.type != null;
});
if (actionablePropertyArray.length === 0) {
continue;
}
fileContents = this.rewriteNodes(actionablePropertyArray, comments, fileContents);
}
switch (node.type) {
case "ClassBody": {
const sortContentsOptions = this._options.sortContents;
if (sortContentsOptions != null) {
fileContents = sortContents(lastClassName, node.body, comments, fileContents, sortContentsOptions);
}
break;
}
case "ClassDeclaration":
case "ClassExpression": {
if (node.id != null) {
lastClassName = node.id.name;
}
break;
}
case "ExportNamedDeclaration":
case "ImportDeclaration": {
if (node.specifiers.length > 1) {
fileContents = sortImportDeclarationSpecifiers(node.specifiers, comments, fileContents, this._options.sortImportDeclarationSpecifiers);
}
break;
}
case "IntersectionTypeAnnotation":
case "TSIntersectionType":
case "TSUnionType":
case "UnionTypeAnnotation": {
fileContents = sortUnionTypeAnnotation(node, comments, fileContents, {
groups: this._options.sortTypeAnnotations,
});
break;
}
case "JSXElement":
case "JSXFragment":
case "JSXOpeningElement": {
fileContents = sortJsxElement(node, comments, fileContents);
break;
}
case "ObjectExpression":
case "ObjectPattern":
case "ObjectTypeAnnotation": {
fileContents = sortObjectTypeAnnotation(node, comments, fileContents, {
groups: this._options.sortTypeAnnotations,
});
break;
}
case "Program": {
fileContents = sortImportDeclarations(node, comments, fileContents, this._options.sortImportDeclarations && {
orderBy: this._options.sortImportDeclarations,
});
break;
}
case "SwitchStatement": {
fileContents = sortSwitchCases(node.cases, comments, fileContents);
break;
}
case "TSInterfaceBody": {
fileContents = sortTSPropertySignatures(node.body, comments, fileContents, {
groups: this._options.sortTypeAnnotations,
});
break;
}
case "TSTypeLiteral": {
fileContents = sortTSPropertySignatures(node.members, comments, fileContents, {
groups: this._options.sortTypeAnnotations,
});
break;
}
}
}
catch (e) {
this.printHelpModeInfo(node, fileContents);
throw e;
}
}
return fileContents;
}
printHelpModeInfo(item, fileContents) {
if (!this._helpModeHasPrintedFilename) {
LogUtils.log(LoggerVerboseOption.Diagnostic, "");
LogUtils.log(LoggerVerboseOption.Diagnostic, this._filename);
}
LogUtils.log(LoggerVerboseOption.Diagnostic, ` - ${item.type} - ${JSON.stringify(item.loc.start)} - ${JSON.stringify(item.loc.end)}`);
LogUtils.log(LoggerVerboseOption.Diagnostic, fileContents.substring(item.range[0], item.range[1]));
}
}