prettier-plugin-imports
Version:
A prettier plugins to sort imports in provided RegEx order
107 lines (93 loc) • 3.58 kB
text/typescript
import { ImportOrderParserPlugin } from '../plugin';
import { PrettierOptions } from '../types';
import { hasPlugin } from '../utils/get-experimental-parser-plugins';
import { preprocessor } from './preprocessor';
import type { SFCDescriptor } from '@vue/compiler-sfc';
export function vuePreprocessor(code: string, options: PrettierOptions) {
try {
const { parse } = require('@vue/compiler-sfc');
const version = require('@vue/compiler-sfc/package.json').version?.split(
'.',
)[0];
const descriptor: SFCDescriptor =
version === '2' ? parse({ source: code }) : parse(code).descriptor;
// 1. Filter valid blocks.
const blocks = [descriptor.script, descriptor.scriptSetup].filter(
(block): block is NonNullable<typeof descriptor.script> =>
Boolean(block?.content),
);
if (!blocks.length) {
return code;
}
// 2. Sort blocks by start offset.
blocks.sort((a, b) => a.loc.start.offset - b.loc.start.offset);
// 3. Replace blocks.
// Using offsets to avoid string replace catching the wrong place and improve efficiency
// see https://github.com/IanVS/prettier-plugin-sort-imports/pull/90
let offset = 0;
let result = '';
for (const block of blocks) {
// https://github.com/vuejs/core/blob/b8fc18c0b23be9a77b05dc41ed452a87a0becf82/packages/compiler-core/src/ast.ts#L74-L80
// The node's range. The `start` is inclusive and `end` is exclusive.
// [start, end)
// @ts-expect-error Some vue versions have a `block.loc`, others have start and end directly on the block
let { start, end } = block;
if ('loc' in block) {
start = block.loc.start.offset;
end = block.loc.end.offset;
}
const preprocessedBlockCode = sortScript(block, options);
result += code.slice(offset, start) + preprocessedBlockCode;
offset = end;
}
// 4. Append the rest.
result += code.slice(offset);
return result;
} catch (err) {
if ((err as NodeJS.ErrnoException).code === 'MODULE_NOT_FOUND') {
console.warn(
'[@ianvs/prettier-plugin-sort-imports]: Could not process .vue file. Please be sure that "@vue/compiler-sfc" is installed in your project.',
);
}
throw err;
}
}
function isTS(lang?: string) {
return lang === 'ts' || lang === 'tsx';
}
/**
* Configures correct babel plugins, sorts imports in a script or setupScript,
* and replaces that script/setupScript within the original code
*
* Much of this was adapted from
* https://github.com/vuejs/vue/blob/49b6bd4264c25ea41408f066a1835f38bf6fe9f1/packages/compiler-sfc/src/compileScript.ts#L118-L134
*
* @param param0 A script or setupScript block of the SFC
* @param options Prettier options
* @returns Original code with sorted imports in the block provided
*/
function sortScript(
{ content, lang }: { content: string; lang?: string },
options: PrettierOptions,
) {
const { importOrderParserPlugins = [] } = options;
let pluginClone = [...importOrderParserPlugins];
const newPlugins: ImportOrderParserPlugin[] = [];
if (!isTS(lang) || lang === 'tsx') {
newPlugins.push('jsx');
} else {
// Remove jsx if typescript and not tsx
pluginClone = pluginClone.filter((p) => p !== 'jsx');
}
newPlugins.push(...pluginClone);
if (isTS(lang)) {
if (!hasPlugin(newPlugins, 'typescript')) {
newPlugins.push('typescript');
}
}
const adjustedOptions = {
...options,
importOrderParserPlugins: newPlugins,
};
return `\n${preprocessor(content, adjustedOptions)}\n`;
}