diagnose duplicate source at violation rather than program node
MODIFY
packages/@romejs/js-compiler/scope/evaluators/index.ts
packages/@romejs/js-compiler/scope/evaluators/index.ts
@@ -33,7 +33,6 @@ import CatchClause from './CatchClause';
import Program from './Program';
import ForStatement from './ForStatement';
import ForOfStatement from './ForOfStatement';
-import ForInStatement from './ForOfStatement';
import VariableDeclarationStatement from './VariableDeclarationStatement';
import TSInterfaceDeclaration from './TSInterfaceDeclaration';
import TSDeclareFunction from './TSDeclareFunction';
@@ -76,7 +75,7 @@ evaluators.set('CatchClause', CatchClause);
evaluators.set('Program', Program);
evaluators.set('ForStatement', ForStatement);
evaluators.set('ForOfStatement', ForOfStatement);
-evaluators.set('ForInStatement', ForInStatement);
+evaluators.set('ForInStatement', ForOfStatement);
evaluators.set('TSInterfaceDeclaration', TSInterfaceDeclaration);
export default evaluators;

MODIFY
packages/@romejs/js-compiler/transforms/lint/__rtests__/__rsnapshots__/duplicateImportSource.snap.md
packages/@romejs/js-compiler/transforms/lint/__rtests__/__rsnapshots__/duplicateImportSource.snap.md
```javascript
Object {
- src: 'import getterReturn from \'../lint/getterReturn\';\n import getterReturn from \'../lint/getterReturn\';\n \n console.log(getterReturn)'
+ src: '\n import foo from \'./testdummy.ts\';\n import {bar} from \'./testdummy.ts\';\n import type {fooType} from \'./testdummy.ts\';\n \n const typedFoo: fooType = {\n type: \'foo\'\n } \n console.log(typedFoo, foo, bar);\n '
suppressions: Array []
diagnostics: Array [
Object {
- category: 'lint/duplicateImport'
+ category: 'parse/js'
+ filename: 'unknown'
+ message: 'A type annotation is only valid inside of a TypeScript or Flow file'
+ mtime: undefined
+ sourceType: 'module'
+ origins: Array [Object {category: 'js-parser'}]
+ end: Object {
+ column: 18
+ index: 149
+ line: 6
+ }
+ start: Object {
+ column: 18
+ index: 149
+ line: 6
+ }
+ advice: Array [
+ log {
+ category: 'info'
+ message: 'Did you mean <emphasis>TypeScript</emphasis>? Change the file extension to <emphasis>.ts</emphasis> or <emphasis>.tsx</emphasis>'
+ }
+ log {
+ category: 'info'
+ message: 'Did you mean <emphasis>Flow</emphasis>? Add a <emphasis>@flow</emphasis> comment annotation to the top of the file'
+ }
+ ]
+ }
+ Object {
+ category: 'lint/duplicateImportSource'
+ filename: 'unknown'
+ fixable: true
+ language: 'js'
+ message: 'This module has already been imported'
+ mtime: undefined
+ sourceType: 'module'
+ origins: Array [Object {category: 'lint'}]
+ end: Object {
+ column: 39
+ index: 78
+ line: 3
+ }
+ start: Object {
+ column: 4
+ index: 43
+ line: 3
+ }
+ advice: Array [
+ log {
+ category: 'info'
+ message: 'Previously imported here'
+ }
+ frame {
+ filename: 'unknown'
+ end: Object {
+ column: 37
+ index: 38
+ line: 2
+ }
+ start: Object {
+ column: 4
+ index: 5
+ line: 2
+ }
+ }
+ ]
+ }
+ Object {
+ category: 'lint/unusedVariables'
filename: 'unknown'
language: 'js'
- message: 'You are importing from the the following sources more than once:\n ../lint/getterReturn'
+ message: 'Unused import <emphasis>fooType</emphasis>'
mtime: undefined
sourceType: 'module'
origins: Array [Object {category: 'lint'}]
end: Object {
- column: 27
- index: 130
+ column: 24
+ index: 103
line: 4
}
start: Object {
- column: 0
- index: 0
- line: 1
+ column: 17
+ index: 96
+ line: 4
}
}
]

MODIFY
packages/@romejs/js-compiler/transforms/lint/__rtests__/duplicateImportSource.ts
packages/@romejs/js-compiler/transforms/lint/__rtests__/duplicateImportSource.ts
@@ -13,12 +13,17 @@ import {
test('duplicate import source', async t => {
const res = await testLint(
- `import getterReturn from '../lint/getterReturn';
- import getterReturn from '../lint/getterReturn';
+ `
+ import foo from './testdummy.ts';
+ import {bar} from './testdummy.ts';
+ import type {fooType} from './testdummy.ts';
- console.log(getterReturn)`,
+ const typedFoo: fooType = {
+ type: 'foo'
+ }
+ console.log(typedFoo, foo, bar);
+ `,
LINT_ENABLED_FORMAT_DISABLED_CONFIG,
);
-
t.snapshot(res);
});

ADD
packages/@romejs/js-compiler/transforms/lint/__rtests__/testdummy.ts
+export default 'foo';
+export const bar = 'baz';
+export type fooType = {
+ type: string;
+};

MODIFY
packages/@romejs/js-compiler/transforms/lint/duplicateImportSource.ts
packages/@romejs/js-compiler/transforms/lint/duplicateImportSource.ts
*/
import {Path} from '@romejs/js-compiler';
-import {AnyNode, ImportDeclaration} from '@romejs/js-ast';
+import {AnyNode, ImportDeclaration, AnyStatement} from '@romejs/js-ast';
+import {SourceLocation} from '@romejs/parser-core';
-export const foo = 'bar';
export default {
name: 'duplicateImport',
enter(path: Path): AnyNode {
const {node} = path;
if (node.type === 'Program') {
- let duplicateImports: String[] = [];
- const filteredImports = (node.body.filter(
- node => node.type === 'ImportDeclaration',
- ) as ImportDeclaration[]).reduce<String[]>((acc, cur) => {
- if (acc.includes(cur.source.value)) {
- if (!duplicateImports.includes(cur.source.value)) {
- duplicateImports = duplicateImports.concat(` ${cur.source.value}`);
- }
- return acc;
+ const seenSources: Map<string, undefined | SourceLocation> = new Map();
+ let shouldFix = false;
+
+ for (const bodyNode of node.body) {
+ if (bodyNode.type === 'ImportDeclaration') {
+ const source = bodyNode.source.value;
+
+ // Allow duplicate sources if the `importKind` is different
+ const sourceKey =
+ bodyNode.importKind === undefined
+ ? source
+ : `${bodyNode.importKind}:${source}`;
+
+ const seenLoc = seenSources.get(sourceKey);
+ if (seenLoc === undefined) {
+ seenSources.set(sourceKey, bodyNode.loc);
} else {
- return [...acc, cur.source.value];
+ shouldFix = true;
+ path.context.addNodeDiagnostic(bodyNode, {
+ fixable: true,
+ category: 'lint/duplicateImportSource',
+ message: 'This module has already been imported',
+ advice: [
+ {
+ type: 'log',
+ category: 'info',
+ message: 'Previously imported here',
+ },
+ {
+ type: 'frame',
+ ...seenLoc,
+ },
+ ],
+ });
+ }
+ }
+ }
+
+ // Defer fixing unless it's totally necessary since there's additional overhead
+ if (shouldFix) {
+ const skipImports: Set<ImportDeclaration> = new Set();
+
+ const newBody: Array<AnyStatement> = [];
+
+ for (let i = 0; i < node.body.length; i++) {
+ const bodyNode = node.body[i];
+
+ if (bodyNode.type === 'ImportDeclaration') {
+ // Skip import if it's already been consumed
+ if (skipImports.has(bodyNode)) {
+ continue;
}
- }, []);
+
+ let specifiers =
+ bodyNode.specifiers === undefined ? [] : bodyNode.specifiers;
+
+ // Find and concat all duplicate imports
+ for (let x = i + 1; x < node.body.length; x++) {
+ const possibleDuplicateNode = node.body[x];
+
if (
- filteredImports.length <
- node.body.filter(node => node.type === 'ImportDeclaration').length
+ possibleDuplicateNode.type === 'ImportDeclaration' &&
+ bodyNode.source.value === possibleDuplicateNode.source.value &&
+ bodyNode.importKind === possibleDuplicateNode.importKind
) {
- path.context.addNodeDiagnostic(node, {
- category: 'lint/duplicateImport',
- message: `You are importing from the the following sources more than once:
- ${duplicateImports}`,
- });
+ skipImports.add(possibleDuplicateNode);
+ if (possibleDuplicateNode.specifiers !== undefined) {
+ specifiers = [
+ ...specifiers,
+ ...possibleDuplicateNode.specifiers,
+ ];
+ }
}
}
+ newBody.push({
+ ...bodyNode,
+ specifiers,
+ });
+ } else {
+ newBody.push(bodyNode);
+ }
+ }
+ return {
+ ...node,
+ body: newBody,
+ };
+ }
+ }
return node;
},
};

diagnose duplicate source at violation rather than program node