Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitd2292bb

Browse files
committed
[Fix]: no-duplicates with type imports
1 parentd5fc8b6 commitd2292bb

File tree

2 files changed

+551
-42
lines changed

2 files changed

+551
-42
lines changed

‎src/rules/no-duplicates.js

Lines changed: 215 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,198 @@ function checkImports(imported, context) {
2727
message,
2828
});
2929
}
30+
3031
}
3132
}
3233
}
3334

34-
functiongetFix(first,rest,sourceCode,context){
35+
functioncheckTypeImports(imported,context){
36+
for(const[module,nodes]ofimported.entries()){
37+
consttypeImports=nodes.filter((node)=>node.importKind==='type');
38+
if(nodes.length>1){
39+
constsomeInlineTypeImports=nodes.filter((node)=>node.specifiers.some((spec)=>spec.importKind==='type'));
40+
if(typeImports.length>0&&someInlineTypeImports.length>0){
41+
constmessage=`'${module}' imported multiple times.`;
42+
constsourceCode=context.getSourceCode();
43+
constfix=getTypeFix(nodes,sourceCode,context);
44+
45+
const[first, ...rest]=nodes;
46+
context.report({
47+
node:first.source,
48+
message,
49+
fix,// Attach the autofix (if any) to the first import.
50+
});
51+
52+
for(constnodeofrest){
53+
context.report({
54+
node:node.source,
55+
message,
56+
});
57+
}
58+
}
59+
}
60+
}
61+
}
62+
63+
functioncheckInlineTypeImports(imported,context){
64+
for(const[module,nodes]ofimported.entries()){
65+
if(nodes.length>1){
66+
constmessage=`'${module}' imported multiple times.`;
67+
constsourceCode=context.getSourceCode();
68+
constfix=getInlineTypeFix(nodes,sourceCode);
69+
70+
const[first, ...rest]=nodes;
71+
context.report({
72+
node:first.source,
73+
message,
74+
fix,// Attach the autofix (if any) to the first import.
75+
});
76+
77+
for(constnodeofrest){
78+
context.report({
79+
node:node.source,
80+
message,
81+
});
82+
}
83+
}
84+
}
85+
}
86+
87+
functionisComma(token){
88+
returntoken.type==='Punctuator'&&token.value===',';
89+
}
90+
91+
functiongetInlineTypeFix(nodes,sourceCode){
92+
returnfixer=>{
93+
constfixes=[];
94+
95+
// if (!semver.satisfies(typescriptPkg.version, '>= 4.5')) {
96+
// throw new Error('Your version of TypeScript does not support inline type imports.');
97+
// }
98+
99+
// push to first import
100+
let[firstImport, ...rest]=nodes;
101+
constvalueImport=nodes.find((n)=>n.specifiers.every((spec)=>spec.importKind==='value'))||nodes.find((n)=>n.specifiers.some((spec)=>spec.type==='ImportDefaultSpecifier'));
102+
if(valueImport){
103+
firstImport=valueImport;
104+
rest=nodes.filter((n)=>n!==firstImport);
105+
}
106+
107+
constnodeTokens=sourceCode.getTokens(firstImport);
108+
// we are moving the rest of the Type or Inline Type imports here.
109+
constnodeClosingBrace=nodeTokens.find(token=>isPunctuator(token,'}'));
110+
// const preferInline = context.options[0] && context.options[0]['prefer-inline'];
111+
if(nodeClosingBrace){
112+
for(constnodeofrest){
113+
// these will be all Type imports, no Value specifiers
114+
// then add inline type specifiers to importKind === 'type' import
115+
for(constspecifierofnode.specifiers){
116+
if(specifier.importKind==='type'){
117+
fixes.push(fixer.insertTextBefore(nodeClosingBrace,`, type${specifier.local.name}`));
118+
}else{
119+
fixes.push(fixer.insertTextBefore(nodeClosingBrace,`,${specifier.local.name}`));
120+
}
121+
}
122+
123+
fixes.push(fixer.remove(node));
124+
}
125+
}else{
126+
// we have a default import only
127+
constdefaultSpecifier=firstImport.specifiers.find((spec)=>spec.type==='ImportDefaultSpecifier');
128+
constinlineTypeImports=[];
129+
for(constnodeofrest){
130+
// these will be all Type imports, no Value specifiers
131+
// then add inline type specifiers to importKind === 'type' import
132+
for(constspecifierofnode.specifiers){
133+
if(specifier.importKind==='type'){
134+
inlineTypeImports.push(`type${specifier.local.name}`);
135+
}else{
136+
inlineTypeImports.push(specifier.local.name);
137+
}
138+
}
139+
140+
fixes.push(fixer.remove(node));
141+
}
142+
143+
fixes.push(fixer.insertTextAfter(defaultSpecifier,`, {${inlineTypeImports.join(', ')}}`));
144+
}
145+
146+
returnfixes;
147+
};
148+
}
149+
150+
functiongetTypeFix(nodes,sourceCode,context){
151+
returnfixer=>{
152+
constfixes=[];
153+
154+
constpreferInline=context.options[0]&&context.options[0]['prefer-inline'];
155+
156+
if(preferInline){
157+
if(!semver.satisfies(typescriptPkg.version,'>= 4.5')){
158+
thrownewError('Your version of TypeScript does not support inline type imports.');
159+
}
160+
161+
// collapse all type imports to the inline type import
162+
consttypeImports=nodes.filter((node)=>node.importKind==='type');
163+
constsomeInlineTypeImports=nodes.filter((node)=>node.specifiers.some((spec)=>spec.importKind==='type'));
164+
// push to first import
165+
constfirstImport=someInlineTypeImports[0];
166+
167+
if(firstImport){
168+
constnodeTokens=sourceCode.getTokens(firstImport);
169+
// we are moving the rest of the Type imports here
170+
constnodeClosingBrace=nodeTokens.find(token=>isPunctuator(token,'}'));
171+
172+
for(constnodeoftypeImports){
173+
for(constspecifierofnode.specifiers){
174+
fixes.push(fixer.insertTextBefore(nodeClosingBrace,`, type${specifier.local.name}`));
175+
}
176+
177+
fixes.push(fixer.remove(node));
178+
}
179+
}
180+
}else{
181+
// move inline types to type imports
182+
consttypeImports=nodes.filter((node)=>node.importKind==='type');
183+
constsomeInlineTypeImports=nodes.filter((node)=>node.specifiers.some((spec)=>spec.importKind==='type'));
184+
185+
constfirstImport=typeImports[0];
186+
187+
if(firstImport){
188+
constnodeTokens=sourceCode.getTokens(firstImport);
189+
// we are moving the rest of the Type imports here
190+
constnodeClosingBrace=nodeTokens.find(token=>isPunctuator(token,'}'));
191+
192+
for(constnodeofsomeInlineTypeImports){
193+
for(constspecifierofnode.specifiers){
194+
if(specifier.importKind==='type'){
195+
fixes.push(fixer.insertTextBefore(nodeClosingBrace,`,${specifier.local.name}`));
196+
}
197+
}
198+
199+
if(node.specifiers.every((spec)=>spec.importKind==='type')){
200+
fixes.push(fixer.remove(node));
201+
}else{
202+
for(constspecifierofnode.specifiers){
203+
if(specifier.importKind==='type'){
204+
constmaybeComma=sourceCode.getTokenAfter(specifier);
205+
if(isComma(maybeComma)){
206+
fixes.push(fixer.remove(maybeComma));
207+
}
208+
// TODO: remove `type`?
209+
fixes.push(fixer.remove(specifier));
210+
}
211+
}
212+
}
213+
}
214+
}
215+
}
216+
217+
returnfixes;
218+
};
219+
}
220+
221+
functiongetFix(first,rest,sourceCode){
35222
// Sorry ESLint <= 3 users, no autofix for you. Autofixing duplicate imports
36223
// requires multiple `fixer.whatever()` calls in the `fix`: We both need to
37224
// update the first one, and remove the rest. Support for multiple
@@ -119,22 +306,13 @@ function getFix(first, rest, sourceCode, context) {
119306

120307
const[specifiersText]=specifiers.reduce(
121308
([result,needsComma,existingIdentifiers],specifier)=>{
122-
constisTypeSpecifier=specifier.importNode.importKind==='type';
123-
124-
constpreferInline=context.options[0]&&context.options[0]['prefer-inline'];
125-
// a user might set prefer-inline but not have a supporting TypeScript version. Flow does not support inline types so this should fail in that case as well.
126-
if(preferInline&&(!typescriptPkg||!semver.satisfies(typescriptPkg.version,'>= 4.5'))){
127-
thrownewError('Your version of TypeScript does not support inline type imports.');
128-
}
129-
130309
// Add *only* the new identifiers that don't already exist, and track any new identifiers so we don't add them again in the next loop
131310
const[specifierText,updatedExistingIdentifiers]=specifier.identifiers.reduce(([text,set],cur)=>{
132311
consttrimmed=cur.trim();// Trim whitespace before/after to compare to our set of existing identifiers
133-
constcurWithType=trimmed.length>0&&preferInline&&isTypeSpecifier ?`type${cur}` :cur;
134312
if(existingIdentifiers.has(trimmed)){
135313
return[text,set];
136314
}
137-
return[text.length>0 ?`${text},${curWithType}` :curWithType,set.add(trimmed)];
315+
return[text.length>0 ?`${text},${cur}` :cur,set.add(trimmed)];
138316
},['',existingIdentifiers]);
139317

140318
return[
@@ -173,7 +351,7 @@ function getFix(first, rest, sourceCode, context) {
173351
// `import def from './foo'` → `import def, {...} from './foo'`
174352
fixes.push(fixer.insertTextAfter(first.specifiers[0],`, {${specifiersText}}`));
175353
}
176-
}elseif(!shouldAddDefault&&openBrace!=null&&closeBrace!=null){
354+
}elseif(!shouldAddDefault&&openBrace!=null&&closeBrace!=null&&specifiersText){
177355
// `import {...} './foo'` → `import {..., ...} from './foo'`
178356
fixes.push(fixer.insertTextBefore(closeBrace,specifiersText));
179357
}
@@ -318,14 +496,18 @@ module.exports = {
318496
nsImported:newMap(),
319497
defaultTypesImported:newMap(),
320498
namedTypesImported:newMap(),
499+
inlineTypesImported:newMap(),
321500
});
322501
}
323502
constmap=moduleMaps.get(n.parent);
324503
if(n.importKind==='type'){
504+
// import type Foo | import type { foo }
325505
returnn.specifiers.length>0&&n.specifiers[0].type==='ImportDefaultSpecifier' ?map.defaultTypesImported :map.namedTypesImported;
326506
}
507+
327508
if(n.specifiers.some((spec)=>spec.importKind==='type')){
328-
returnmap.namedTypesImported;
509+
// import { type foo }
510+
returnmap.inlineTypesImported;
329511
}
330512

331513
returnhasNamespace(n) ?map.nsImported :map.imported;
@@ -350,6 +532,26 @@ module.exports = {
350532
checkImports(map.nsImported,context);
351533
checkImports(map.defaultTypesImported,context);
352534
checkImports(map.namedTypesImported,context);
535+
536+
constduplicatedImports=newMap([...map.inlineTypesImported]);
537+
map.imported.forEach((value,key)=>{
538+
if(duplicatedImports.has(key)){
539+
duplicatedImports.get(key).push(...value);
540+
}else{
541+
duplicatedImports.set(key,[value]);
542+
}
543+
});
544+
checkInlineTypeImports(duplicatedImports,context);
545+
546+
constduplicatedTypeImports=newMap([...map.inlineTypesImported]);
547+
map.namedTypesImported.forEach((value,key)=>{
548+
if(duplicatedTypeImports.has(key)){
549+
duplicatedTypeImports.get(key).push(...value);
550+
}else{
551+
duplicatedTypeImports.set(key,value);
552+
}
553+
});
554+
checkTypeImports(duplicatedTypeImports,context);
353555
}
354556
},
355557
};

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp