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

Add support for TypeScript import assignments #149

Merged
merged 13 commits into from
Feb 8, 2024
35 changes: 27 additions & 8 deletions src/imports.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ const defaultGroups = [
// Relative imports.
// Anything that starts with a dot.
["^\\."],
// Namespace import.
// Anything that starts with an equal sign.
["^= "],
];

module.exports = {
Expand Down Expand Up @@ -56,7 +59,7 @@ module.exports = {
const parents = new Set();

return {
ImportDeclaration: (node) => {
"ImportDeclaration,TSImportEqualsDeclaration": (node) => {
parents.add(node.parent);
},

Expand Down Expand Up @@ -132,12 +135,20 @@ function makeSortedItems(items, outerGroups) {

// Exclude "ImportDefaultSpecifier" – the "def" in `import def, {a, b}`.
function getSpecifiers(importNode) {
return importNode.specifiers.filter((node) => isImportSpecifier(node));
switch (importNode.type) {
case "TSImportEqualsDeclaration":
return [];
default:
return importNode.specifiers.filter((node) => isImportSpecifier(node));
}
}

// Full import statement.
function isImport(node) {
return node.type === "ImportDeclaration";
return (
node.type === "ImportDeclaration" ||
node.type === "TSImportEqualsDeclaration"
lydell marked this conversation as resolved.
Show resolved Hide resolved
);
}

// import def, { a, b as c, type d } from "A"
Expand All @@ -150,9 +161,17 @@ function isImportSpecifier(node) {
// But not: import {} from "setup"
// And not: import type {} from "setup"
function isSideEffectImport(importNode, sourceCode) {
return (
importNode.specifiers.length === 0 &&
(!importNode.importKind || importNode.importKind === "value") &&
!shared.isPunctuator(sourceCode.getFirstToken(importNode, { skip: 1 }), "{")
);
switch (importNode.type) {
case "TSImportEqualsDeclaration":
return false;
default:
return (
importNode.specifiers.length === 0 &&
(!importNode.importKind || importNode.importKind === "value") &&
!shared.isPunctuator(
sourceCode.getFirstToken(importNode, { skip: 1 }),
"{"
)
);
}
}
67 changes: 64 additions & 3 deletions src/shared.js
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ function getImportExportItems(
const [start] = all[0].range;
const [, end] = all[all.length - 1].range;

const source = getSource(node);
const source = getSource(sourceCode, node);

return {
node,
Expand Down Expand Up @@ -795,8 +795,69 @@ function isNewline(node) {
return node.type === "Newline";
}

function getSource(node) {
const source = node.source.value;
function getSourceFromTSQualifiedName(sourceCode, node) {
let left;
let right;
switch (node.left.type) {
case "Identifier": {
left = node.left.name;
break;
}
case "TSQualifiedName": {
left = getSourceFromTSQualifiedName(sourceCode, node.left);
break;
}
default: {
left = ``;
break;
}
}
switch (node.right.type) {
case "Identifier": {
right = `.${node.right.name}`;
break;
}
default: {
right = ``;
break;
}
}
return left + right;
}

function getSourceFromModuleReference(sourceCode, node) {
switch (node.type) {
case "TSExternalModuleReference": {
switch (node.expression.type) {
case "Literal": {
return node.expression.value;
}
default: {
return sourceCode.text.slice(...node.expression.range);
}
}
}
case "TSQualifiedName": {
return `= ${getSourceFromTSQualifiedName(sourceCode, node)}`;
}
default: {
return ``;
}
}
}

function getSource(sourceCode, node) {
let source;
switch (node.type) {
case "TSImportEqualsDeclaration": {
source = getSourceFromModuleReference(sourceCode, node.moduleReference);
break;
}
default: {
source = node.source.value;
break;
}
}

return {
// Sort by directory level rather than by string length.
Expand Down
66 changes: 66 additions & 0 deletions test/imports.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2037,6 +2037,72 @@ const typescriptTests = {
},
errors: 3,
},

// Import assignments.
{
code: input`
|import { Namespace } from './namespace';
|import Foo = Namespace.Foo;
|import { bar } from './a';
`,
output: (actual) => {
expect(actual).toMatchInlineSnapshot(`
|import { bar } from './a';
|import { Namespace } from './namespace';
|
|import Foo = Namespace.Foo;
`);
},
errors: 1,
},
{
code: input`
|import B = require('./b');
|import A = require('./a');
|import Foo = require('foo');
|import Bar = require('../foo');
`,
output: (actual) => {
expect(actual).toMatchInlineSnapshot(`
|import Foo = require('foo');
|
|import Bar = require('../foo');
|import A = require('./a');
|import B = require('./b');
`);
},
errors: 1,
},
{
code: input`
|import Foo = require('foo');
|import Bar = require(cool().thing);
|import Bar = require(1 + 1);
`,
output: (actual) => {
expect(actual).toMatchInlineSnapshot(`
|import Bar = require(1 + 1);
|import Bar = require(cool().thing);
|import Foo = require('foo');
`);
},
errors: 1,
},
{
code: input`
|import B = Namespace/*aaaa*/.B;
|import AB = Namespace.A.B;
|import A = Namespace.A.A;
`,
output: (actual) => {
expect(actual).toMatchInlineSnapshot(`
|import A = Namespace.A.A;
|import AB = Namespace.A.B;
|import B = Namespace/*aaaa*/.B;
`);
},
errors: 1,
},
],
};

Expand Down