Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Allow Explicit Separator after Top-of-file-comments #92

Merged
merged 14 commits into from
May 19, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 9 additions & 34 deletions src/preprocessors/preprocessor.ts
Original file line number Diff line number Diff line change
@@ -1,53 +1,28 @@
import { parse as babelParser, ParserOptions } from '@babel/parser';
import traverse, { NodePath } from '@babel/traverse';
import { ImportDeclaration, isTSModuleDeclaration } from '@babel/types';
import semver from 'semver';

import { TYPES_SPECIAL_WORD } from '../constants';
import { PrettierOptions } from '../types';
import { getCodeFromAst } from '../utils/get-code-from-ast';
import { getExperimentalParserPlugins } from '../utils/get-experimental-parser-plugins';
import { getSortedNodes } from '../utils/get-sorted-nodes';
import { examineAndNormalizePluginOptions } from '../utils/normalize-plugin-options';

export function preprocessor(code: string, options: PrettierOptions): string {
const { importOrderParserPlugins, importOrder } = options;
let { importOrderTypeScriptVersion } = options;
const isTSSemverValid = semver.valid(importOrderTypeScriptVersion);
const { plugins, ...remainingOptions } =
examineAndNormalizePluginOptions(options);

if (!isTSSemverValid) {
console.warn(
`[@ianvs/prettier-plugin-sort-imports]: The option importOrderTypeScriptVersion is not a valid semver version and will be ignored.`,
);
importOrderTypeScriptVersion = '1.0.0';
}

// Do not combine type and value imports if `<TYPES>` is specified explicitly
let importOrderCombineTypeAndValueImports = importOrder.some((group) =>
group.includes(TYPES_SPECIAL_WORD),
)
? false
: true;

const allOriginalImportNodes: ImportDeclaration[] = [];
const parserOptions: ParserOptions = {
sourceType: 'module',
attachComment: true,
plugins: getExperimentalParserPlugins(importOrderParserPlugins),
plugins,
};

// Disable importOrderCombineTypeAndValueImports if typescript is not set to a version that supports it
if (
parserOptions.plugins?.includes('typescript') &&
semver.lt(importOrderTypeScriptVersion, '4.5.0')
) {
importOrderCombineTypeAndValueImports = false;
}

const ast = babelParser(code, parserOptions);

const directives = ast.program.directives;
const interpreter = ast.program.interpreter;

const allOriginalImportNodes: ImportDeclaration[] = [];
traverse(ast, {
ImportDeclaration(path: NodePath<ImportDeclaration>) {
const tsModuleParent = path.findParent((p) =>
Expand All @@ -64,10 +39,10 @@ export function preprocessor(code: string, options: PrettierOptions): string {
return code;
}

const nodesToOutput = getSortedNodes(allOriginalImportNodes, {
importOrder,
importOrderCombineTypeAndValueImports,
});
const nodesToOutput = getSortedNodes(
allOriginalImportNodes,
remainingOptions,
);

return getCodeFromAst({
nodesToOutput,
Expand Down
22 changes: 21 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { ParserPlugin } from '@babel/parser';
import {
type EmptyStatement,
type ExpressionStatement,
Expand Down Expand Up @@ -46,13 +47,28 @@ export type SomeSpecifier =
| ImportNamespaceSpecifier;
export type ImportRelated = ImportOrLine | SomeSpecifier;

export interface InspectedAndNormalizedOptions {
IanVS marked this conversation as resolved.
Show resolved Hide resolved
importOrder: PrettierOptions['importOrder'];
importOrderCombineTypeAndValueImports: boolean;
hasAnyCustomGroupSeparatorsInImportOrder: boolean;
provideGapAfterTopOfFileComments: boolean;
plugins: ParserPlugin[];
}

export type GetSortedNodes = (
nodes: ImportDeclaration[],
options: Pick<PrettierOptions, 'importOrder'> & {
options: Pick<InspectedAndNormalizedOptions, 'importOrder'> & {
importOrderCombineTypeAndValueImports: boolean;
hasAnyCustomGroupSeparatorsInImportOrder?: boolean;
provideGapAfterTopOfFileComments?: boolean;
},
) => ImportOrLine[];

export type GetSortedNodesByImportOrder = (
nodes: ImportDeclaration[],
options: Pick<InspectedAndNormalizedOptions, 'importOrder'>,
) => ImportOrLine[];

export type GetChunkTypeOfNode = (node: ImportDeclaration) => ChunkType;

export type GetImportFlavorOfNode = (node: ImportDeclaration) => FlavorType;
Expand All @@ -65,3 +81,7 @@ export type MergeNodesWithMatchingImportFlavors = (
export type ExplodeTypeAndValueSpecifiers = (
nodes: ImportDeclaration[],
) => ImportDeclaration[];

export interface CommentAttachmentOptions {
provideGapAfterTopOfFileComments?: boolean;
}
3 changes: 2 additions & 1 deletion src/utils/__tests__/get-all-comments-from-nodes.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ import { expect, test } from 'vitest';
import { getAllCommentsFromNodes } from '../get-all-comments-from-nodes';
import { getImportNodes } from '../get-import-nodes';
import { getSortedNodes } from '../get-sorted-nodes';
import { testingOnly } from '../normalize-plugin-options';

const getSortedImportNodes = (code: string, options?: ParserOptions) => {
const importNodes: ImportDeclaration[] = getImportNodes(code, options);

return getSortedNodes(importNodes, {
importOrder: [],
importOrder: testingOnly.normalizeImportOrderOption([]),
importOrderCombineTypeAndValueImports: true,
});
};
Expand Down
7 changes: 5 additions & 2 deletions src/utils/__tests__/get-code-from-ast.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import { expect, test } from 'vitest';
import { getCodeFromAst } from '../get-code-from-ast';
import { getImportNodes } from '../get-import-nodes';
import { getSortedNodes } from '../get-sorted-nodes';
import { testingOnly } from '../normalize-plugin-options';

const emptyImportOrder = testingOnly.normalizeImportOrderOption([]);

test('sorts imports correctly', () => {
const code = `import z from 'z';
Expand All @@ -15,7 +18,7 @@ import a from 'a';
`;
const importNodes = getImportNodes(code);
const sortedNodes = getSortedNodes(importNodes, {
importOrder: [],
importOrder: emptyImportOrder,
importOrderCombineTypeAndValueImports: true,
});
const formatted = getCodeFromAst({
Expand Down Expand Up @@ -47,7 +50,7 @@ import type {See} from 'c';
`;
const importNodes = getImportNodes(code, { plugins: ['typescript'] });
const sortedNodes = getSortedNodes(importNodes, {
importOrder: [],
importOrder: emptyImportOrder,
importOrderCombineTypeAndValueImports: true,
});
const formatted = getCodeFromAst({
Expand Down
4 changes: 2 additions & 2 deletions src/utils/__tests__/get-comment-registry.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
attachCommentsToOutputNodes,
CommentAssociation,
getCommentRegistryFromImportDeclarations,
testingOnlyExports,
testingOnly,
} from '../get-comment-registry';

describe('getCommentRegistryFromImportDeclarations', () => {
Expand Down Expand Up @@ -64,7 +64,7 @@ describe('attachCommentsToOutputNodes', () => {
needsTopOfFileOwner: true,
comment,
ownerIsSpecifier: false,
commentId: testingOnlyExports.nodeId(comment),
commentId: testingOnly.nodeId(comment),
owner: firstImport,
association: CommentAssociation.trailing,
processingPriority: 0,
Expand Down
38 changes: 22 additions & 16 deletions src/utils/__tests__/get-sorted-nodes-by-import-order.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { getImportNodes } from '../get-import-nodes';
import { getSortedNodesByImportOrder } from '../get-sorted-nodes-by-import-order';
import { getSortedNodesModulesNames } from '../get-sorted-nodes-modules-names';
import { getSortedNodesNamesAndNewlines } from '../get-sorted-nodes-names-and-newlines';
import { testingOnly } from '../normalize-plugin-options';

const code = `// first comment
// second comment
Expand All @@ -28,8 +29,7 @@ import Xa from 'Xa';
test('it returns all sorted nodes', () => {
const result = getImportNodes(code);
const sorted = getSortedNodesByImportOrder(result, {
importOrder: ['^[./]'],
importOrderCombineTypeAndValueImports: true,
importOrder: testingOnly.normalizeImportOrderOption(['^[./]']),
}) as ImportDeclaration[];

expect(getSortedNodesNamesAndNewlines(sorted)).toEqual([
Expand Down Expand Up @@ -77,8 +77,13 @@ test('it returns all sorted nodes', () => {
test('it returns all sorted nodes with sort order', () => {
const result = getImportNodes(code);
const sorted = getSortedNodesByImportOrder(result, {
importOrder: ['^a$', '^t$', '^k$', '^B', '^[./]'],
importOrderCombineTypeAndValueImports: true,
importOrder: testingOnly.normalizeImportOrderOption([
'^a$',
'^t$',
'^k$',
'^B',
'^[./]',
]),
}) as ImportDeclaration[];
expect(getSortedNodesNamesAndNewlines(sorted)).toEqual([
'node:fs/promises',
Expand Down Expand Up @@ -133,8 +138,7 @@ import {type B, A} from 'z';
plugins: ['typescript'],
});
const sorted = getSortedNodesByImportOrder(result, {
importOrder: ['^[./]'],
importOrderCombineTypeAndValueImports: true,
importOrder: testingOnly.normalizeImportOrderOption(['^[./]']),
}) as ImportDeclaration[];
expect(getSortedNodesNamesAndNewlines(sorted)).toEqual(['k', 't', 'z']);
expect(
Expand All @@ -153,8 +157,7 @@ import {type B, A} from 'z';
test('it returns all sorted nodes with builtin specifiers at the top', () => {
const result = getImportNodes(code);
const sorted = getSortedNodesByImportOrder(result, {
importOrder: ['^[./]'],
importOrderCombineTypeAndValueImports: true,
importOrder: testingOnly.normalizeImportOrderOption(['^[./]']),
}) as ImportDeclaration[];

expect(getSortedNodesNamesAndNewlines(sorted)).toEqual([
Expand All @@ -179,8 +182,13 @@ test('it returns all sorted nodes with builtin specifiers at the top', () => {
test('it returns all sorted nodes with custom third party modules and builtins at top', () => {
const result = getImportNodes(code);
const sorted = getSortedNodesByImportOrder(result, {
importOrder: ['^a$', '<THIRD_PARTY_MODULES>', '^t$', '^k$', '^[./]'],
importOrderCombineTypeAndValueImports: true,
Copy link
Owner

@IanVS IanVS May 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is lost if we don't specify a importOrderTypeScriptVersion, right? I don't think you meant to change that behavior in this test.

Edit: Maybe it's fine because that only applies to typescript files, and we weren't actually combining anything in these tests? (I didn't look)

importOrder: testingOnly.normalizeImportOrderOption([
'^a$',
'<THIRD_PARTY_MODULES>',
'^t$',
'^k$',
'^[./]',
]),
}) as ImportDeclaration[];
expect(getSortedNodesNamesAndNewlines(sorted)).toEqual([
'node:fs/promises',
Expand All @@ -204,15 +212,14 @@ test('it returns all sorted nodes with custom third party modules and builtins a
test('it returns all sorted nodes with custom separation', () => {
const result = getImportNodes(code);
const sorted = getSortedNodesByImportOrder(result, {
importOrder: [
importOrder: testingOnly.normalizeImportOrderOption([
'^a$',
'<THIRD_PARTY_MODULES>',
'^t$',
'',
'^k$',
'^[./]',
],
importOrderCombineTypeAndValueImports: true,
]),
}) as ImportDeclaration[];
expect(getSortedNodesNamesAndNewlines(sorted)).toEqual([
'node:fs/promises',
Expand All @@ -237,7 +244,7 @@ test('it returns all sorted nodes with custom separation', () => {
test('it does not add multiple custom import separators', () => {
const result = getImportNodes(code);
const sorted = getSortedNodesByImportOrder(result, {
importOrder: [
importOrder: testingOnly.normalizeImportOrderOption([
'^a$',
'<THIRD_PARTY_MODULES>',
'^t$',
Expand All @@ -246,8 +253,7 @@ test('it does not add multiple custom import separators', () => {
'',
'^k$',
'^[./]',
],
importOrderCombineTypeAndValueImports: true,
]),
}) as ImportDeclaration[];
expect(getSortedNodesNamesAndNewlines(sorted)).toEqual([
'node:fs/promises',
Expand Down
3 changes: 2 additions & 1 deletion src/utils/__tests__/get-sorted-nodes.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { getImportNodes } from '../get-import-nodes';
import { getSortedNodes } from '../get-sorted-nodes';
import { getSortedNodesModulesNames } from '../get-sorted-nodes-modules-names';
import { getSortedNodesNamesAndNewlines } from '../get-sorted-nodes-names-and-newlines';
import { testingOnly } from '../normalize-plugin-options';

const code = `
import "se3";
Expand All @@ -28,7 +29,7 @@ import "se2";
test('it returns all sorted nodes, preserving the order side effect nodes', () => {
const result = getImportNodes(code);
const sorted = getSortedNodes(result, {
importOrder: [],
importOrder: testingOnly.normalizeImportOrderOption([]),
importOrderCombineTypeAndValueImports: true,
}) as ImportDeclaration[];
expect(getSortedNodesNamesAndNewlines(sorted)).toEqual([
Expand Down
8 changes: 5 additions & 3 deletions src/utils/__tests__/merge-nodes-with-matching-flavors.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import { expect, test } from 'vitest';
import { getCodeFromAst } from '../get-code-from-ast';
import { getImportNodes } from '../get-import-nodes';
import { getSortedNodes } from '../get-sorted-nodes';
import { examineAndNormalizePluginOptions } from '../normalize-plugin-options';

const defaultOptions = {
const defaultOptions = examineAndNormalizePluginOptions({
importOrder: [''], // Separate side-effect and ignored chunks, for easier test readability
importOrderCombineTypeAndValueImports: true,
};
importOrderTypeScriptVersion: '5.0.0',
importOrderParserPlugins: [],
});

test('should merge duplicate imports within a given chunk', () => {
const code = `
Expand Down
3 changes: 2 additions & 1 deletion src/utils/__tests__/remove-nodes-from-original-code.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { expect, test } from 'vitest';
import { getAllCommentsFromNodes } from '../get-all-comments-from-nodes';
import { getImportNodes } from '../get-import-nodes';
import { getSortedNodes } from '../get-sorted-nodes';
import { testingOnly } from '../normalize-plugin-options';
import { removeNodesFromOriginalCode } from '../remove-nodes-from-original-code';

const code = `"some directive";// first comment
Expand All @@ -23,7 +24,7 @@ test('it should remove nodes from the original code', () => {
const ast = babelParser(code, { sourceType: 'module' });
const importNodes = getImportNodes(code);
const sortedNodes = getSortedNodes(importNodes, {
importOrder: [],
importOrder: testingOnly.normalizeImportOrderOption([]),
IanVS marked this conversation as resolved.
Show resolved Hide resolved
importOrderCombineTypeAndValueImports: true,
});
const allCommentsFromImports = getAllCommentsFromNodes(sortedNodes);
Expand Down
10 changes: 8 additions & 2 deletions src/utils/adjust-comments-on-sorted-nodes.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { removeComments, type ImportDeclaration } from '@babel/types';

import { ImportOrLine } from '../types';
import { CommentAttachmentOptions, ImportOrLine } from '../types';
import {
attachCommentsToOutputNodes,
getCommentRegistryFromImportDeclarations,
Expand All @@ -17,6 +17,7 @@ import {
export const adjustCommentsOnSortedNodes = (
originalDeclarationNodes: readonly ImportDeclaration[],
finalNodes: readonly ImportOrLine[],
options: CommentAttachmentOptions,
) => {
const outputNodes: ImportDeclaration[] = finalNodes.filter(
(n) => n.type === 'ImportDeclaration',
Expand Down Expand Up @@ -46,7 +47,12 @@ export const adjustCommentsOnSortedNodes = (
return noDirectCommentsNode;
});

attachCommentsToOutputNodes(registry, finalNodesClone, firstImport);
attachCommentsToOutputNodes(
registry,
finalNodesClone,
firstImport,
options,
);

return finalNodesClone;
};
Loading