Skip to content

Commit 528dabf

Browse files
authored
Merge pull request #36 from buildo/infer-quote
Respect existing quote type when possible
2 parents 6e7720c + 58687d4 commit 528dabf

File tree

6 files changed

+79
-39
lines changed

6 files changed

+79
-39
lines changed

src/rules/no-lib-imports.ts

+11-9
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { createRule } from "../utils";
1+
import { ASTUtils } from "@typescript-eslint/experimental-utils";
2+
import { createRule, inferQuote } from "../utils";
23

34
export default createRule({
45
name: "no-lib-imports",
@@ -20,18 +21,14 @@ export default createRule({
2021
create(context) {
2122
return {
2223
ImportDeclaration(node) {
23-
const sourceValue = node.source.raw;
24-
const openQuote = /^['"]{1}/;
25-
const forbiddenImportPattern = /fp-ts\/lib\//;
24+
const sourceValue = ASTUtils.getStringIfConstant(node.source);
25+
const forbiddenImportPattern = /^fp-ts\/lib\//;
2626

27-
if (
28-
sourceValue.match(openQuote.source + forbiddenImportPattern.source)
29-
) {
27+
if (sourceValue?.match(forbiddenImportPattern.source)) {
3028
const fixedImportSource = sourceValue.replace(
3129
forbiddenImportPattern,
3230
"fp-ts/"
3331
);
34-
3532
context.report({
3633
node: node.source,
3734
messageId: "importNotAllowed",
@@ -40,7 +37,12 @@ export default createRule({
4037
fixed: fixedImportSource,
4138
},
4239
fix(fixer) {
43-
return fixer.replaceText(node.source, fixedImportSource);
40+
const quote = inferQuote(node.source);
41+
42+
return fixer.replaceText(
43+
node.source,
44+
`${quote}${fixedImportSource}${quote}`
45+
);
4446
},
4547
});
4648
}

src/rules/no-module-imports.ts

+8-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {
44
} from "@typescript-eslint/experimental-utils";
55
import { array } from "fp-ts";
66
import { pipe } from "fp-ts/function";
7-
import { contextUtils, createRule } from "../utils";
7+
import { contextUtils, createRule, inferQuote } from "../utils";
88

99
type Options = [{ allowTypes?: boolean; allowedModules?: string[] }];
1010

@@ -65,7 +65,7 @@ export default createRule<Options, MessageIds>({
6565

6666
return {
6767
ImportDeclaration(node) {
68-
const sourceValue = node.source.value?.toString();
68+
const sourceValue = ASTUtils.getStringIfConstant(node.source);
6969
if (sourceValue) {
7070
const forbiddenImportPattern = /^fp-ts\/(.+)/;
7171
const matches = sourceValue.match(forbiddenImportPattern);
@@ -152,7 +152,12 @@ export default createRule<Options, MessageIds>({
152152

153153
return [
154154
...importFixes,
155-
...addNamedImportIfNeeded(indexExport, "fp-ts", fixer),
155+
...addNamedImportIfNeeded(
156+
indexExport,
157+
"fp-ts",
158+
inferQuote(node.source),
159+
fixer
160+
),
156161
...referencesFixes,
157162
];
158163
},

src/rules/no-pipeable.ts

+26-25
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { ASTUtils, TSESTree } from "@typescript-eslint/experimental-utils";
2-
import { createRule } from "../utils";
2+
import { createRule, inferQuote } from "../utils";
33

44
export default createRule({
55
name: "no-pipeable",
@@ -24,30 +24,31 @@ export default createRule({
2424
return {
2525
ImportDeclaration(node) {
2626
const sourceValue = ASTUtils.getStringIfConstant(node.source);
27-
if (sourceValue) {
28-
const pipeableSourcePattern = /^fp-ts\/(lib\/)?pipeable/;
29-
30-
if (sourceValue.match(pipeableSourcePattern)) {
31-
if (
32-
node.specifiers.find(
33-
(importClause) =>
34-
(importClause as TSESTree.ImportSpecifier).imported?.name ===
35-
"pipe"
36-
)
37-
) {
38-
context.report({
39-
node: node.source,
40-
messageId: "importPipeFromFunction",
41-
fix(fixer) {
42-
return fixer.replaceText(node.source, `"fp-ts/function"`);
43-
},
44-
});
45-
} else {
46-
context.report({
47-
node: node.source,
48-
messageId: "pipeableIsDeprecated",
49-
});
50-
}
27+
const pipeableSourcePattern = /^fp-ts\/(lib\/)?pipeable/;
28+
if (sourceValue?.match(pipeableSourcePattern)) {
29+
if (
30+
node.specifiers.find(
31+
(importClause) =>
32+
(importClause as TSESTree.ImportSpecifier).imported?.name ===
33+
"pipe"
34+
)
35+
) {
36+
context.report({
37+
node: node.source,
38+
messageId: "importPipeFromFunction",
39+
fix(fixer) {
40+
const quote = inferQuote(node.source);
41+
return fixer.replaceText(
42+
node.source,
43+
`${quote}fp-ts/function${quote}`
44+
);
45+
},
46+
});
47+
} else {
48+
context.report({
49+
node: node.source,
50+
messageId: "pipeableIsDeprecated",
51+
});
5152
}
5253
}
5354
},

src/utils.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,11 @@ export function inferIndent(node: TSESTree.Node): string {
160160
return new Array(node.loc.start.column + 1).join(" ");
161161
}
162162

163+
type Quote = "'" | '"';
164+
export function inferQuote(node: TSESTree.Literal): Quote {
165+
return node.raw[0] === "'" ? "'" : '"';
166+
}
167+
163168
export const contextUtils = <
164169
TMessageIds extends string,
165170
TOptions extends readonly unknown[]
@@ -201,6 +206,7 @@ export const contextUtils = <
201206
function addNamedImportIfNeeded(
202207
name: string,
203208
moduleName: string,
209+
quote: Quote,
204210
fixer: TSESLint.RuleFixer
205211
): Array<TSESLint.RuleFix> {
206212
return pipe(
@@ -215,15 +221,15 @@ export const contextUtils = <
215221
() => [
216222
fixer.insertTextAfterRange(
217223
[0, 0],
218-
`import { ${name} } from "${moduleName}"\n`
224+
`import { ${name} } from ${quote}${moduleName}${quote}\n`
219225
),
220226
],
221227
(lastImport) =>
222228
// other imports founds in this file, insert the import after the last one
223229
[
224230
fixer.insertTextAfterRange(
225231
[lastImport.range[0], lastImport.range[1] + 1],
226-
`import { ${name} } from "${moduleName}"\n`
232+
`import { ${name} } from ${quote}${moduleName}${quote}\n`
227233
),
228234
]
229235
)

tests/rules/no-module-imports.test.ts

+17
Original file line numberDiff line numberDiff line change
@@ -125,5 +125,22 @@ ruleTester.run("no-module-imports", rule, {
125125
const z = option.fromNullable(null)
126126
`,
127127
},
128+
{
129+
code: stripIndent`
130+
import { some } from 'fp-ts/Option'
131+
132+
const v = some(42)
133+
`,
134+
errors: [
135+
{
136+
messageId: "importNotAllowed",
137+
},
138+
],
139+
output: stripIndent`
140+
import { option } from 'fp-ts'
141+
142+
const v = option.some(42)
143+
`,
144+
},
128145
],
129146
});

tests/rules/no-pipeable.test.ts

+9
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,15 @@ ruleTester.run("no-pipeable", rule, {
3232
],
3333
output: 'import { pipe } from "fp-ts/function"',
3434
},
35+
{
36+
code: "import { pipe } from 'fp-ts/pipeable'",
37+
errors: [
38+
{
39+
messageId: "importPipeFromFunction",
40+
},
41+
],
42+
output: "import { pipe } from 'fp-ts/function'",
43+
},
3544
{
3645
code: 'import { pipeable } from "fp-ts/pipeable"',
3746
errors: [

0 commit comments

Comments
 (0)