Skip to content

Commit ba41f6e

Browse files
committed
fix: apply fixes for one rule at a time
Rules may touch the same line/column pairs, so every rule must be re-applied to ensure its fixes are still correct. Closes #26
1 parent 2edde01 commit ba41f6e

File tree

2 files changed

+67
-64
lines changed

2 files changed

+67
-64
lines changed

src/apply-fixes.js

-10
This file was deleted.

src/format.js

+67-54
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1+
import { extname } from "path";
12
import { readFileSync } from "fs";
2-
import { applyTSLintAllFixes } from "./apply-fixes";
33
import { requireModule, getModulePath, getPrettierConfig } from "./utils";
44

55
/**
@@ -25,74 +25,87 @@ import { requireModule, getModulePath, getPrettierConfig } from "./utils";
2525
* @return {String} - the formatted string
2626
*/
2727
export default function format(options) {
28-
const {
29-
filePath,
30-
text = readFileSync(filePath, "utf8"),
31-
tslintPath = getModulePath(filePath, "tslint"),
32-
prettierPath = getModulePath(filePath, "prettier"),
33-
prettierLast,
34-
fallbackPrettierOptions,
35-
} = options;
28+
const { filePath } = options;
3629

37-
const tslintConfig = Object.assign(
38-
{},
30+
const tslintFix = createTSLintFix(
3931
options.tslintConfig,
40-
getTSLintConfig(filePath, tslintPath)
41-
);
42-
43-
const prettierOptions = Object.assign(
44-
{},
45-
filePath && { filepath: filePath },
46-
getPrettierConfig(filePath),
47-
options.prettierOptions
32+
options.tslintPath || getModulePath(filePath, "tslint")
4833
);
4934

5035
const prettify = createPrettify(
51-
prettierOptions || fallbackPrettierOptions || {},
52-
prettierPath
36+
options.prettierOptions || options.fallbackPrettierOptions || {},
37+
options.prettierPath || getModulePath(filePath, "prettier")
5338
);
54-
const tslintFix = createTSLintFix(tslintConfig, tslintPath);
5539

56-
if (prettierLast) {
57-
return prettify(tslintFix(text, filePath));
58-
}
59-
return tslintFix(prettify(text), filePath);
40+
const text = options.text || readFileSync(filePath, "utf8");
41+
return options.prettierLast
42+
? prettify(tslintFix(text, filePath), filePath)
43+
: tslintFix(prettify(text, filePath), filePath);
6044
}
6145

6246
function createPrettify(formatOptions, prettierPath) {
63-
return function prettify(text) {
64-
const prettier = requireModule(prettierPath);
65-
try {
66-
const output = prettier.format(text, formatOptions);
67-
return output;
68-
} catch (error) {
69-
throw error;
70-
}
47+
const prettier = requireModule(prettierPath);
48+
return function prettify(text, filePath) {
49+
return prettier.format(
50+
text,
51+
Object.assign(
52+
{},
53+
formatOptions,
54+
getPrettierConfig(filePath),
55+
filePath && { filepath: filePath }
56+
)
57+
);
7158
};
7259
}
7360

74-
function createTSLintFix(tslintConfig, tslintPath) {
61+
function createTSLintFix(defaultLintConfig, tslintPath) {
62+
const tslint = requireModule(tslintPath);
63+
const { findConfiguration } = tslint.Configuration;
64+
65+
// Adapted from: https://github.com/palantir/tslint/blob/5.12.0/src/linter.ts
7566
return function tslintFix(text, filePath) {
76-
const tslint = requireModule(tslintPath);
77-
try {
78-
const linter = new tslint.Linter({
79-
fix: false,
80-
formatter: "json",
81-
});
67+
// TODO: Use the "fix" option of `new tslint.Linter()` once the following
68+
// issue is triaged: https://github.com/palantir/tslint/issues/4411
69+
const linter = new tslint.Linter({
70+
fix: false, // Disabled to avoid file operations.
71+
formatter: "json",
72+
});
73+
74+
const lintConfig = Object.assign(
75+
{},
76+
defaultLintConfig,
77+
findConfiguration(null, filePath).results
78+
);
8279

83-
linter.lint(filePath, text, tslintConfig);
84-
return applyTSLintAllFixes(linter.getResult(), text, tslint);
85-
} catch (error) {
86-
throw error;
80+
linter.lint(filePath, text, lintConfig);
81+
const { failures } = linter.getResult();
82+
if (!failures.length) {
83+
return text;
8784
}
88-
};
89-
}
9085

91-
function getTSLintConfig(filePath, tslintPath) {
92-
const tslint = requireModule(tslintPath);
93-
try {
94-
return tslint.Configuration.findConfiguration(null, filePath).results;
95-
} catch (error) {
96-
return { rules: {} };
97-
}
86+
// This is a private method, but we're using it as a workaround.
87+
const enabledRules = linter.getEnabledRules(
88+
lintConfig,
89+
extname(filePath) === ".js"
90+
);
91+
92+
// To keep rules from interfering with one another, we apply their fixes one
93+
// rule at a time. More info: https://github.com/azz/prettier-tslint/issues/26
94+
return enabledRules.reduce((text, rule) => {
95+
const { ruleName } = rule.getOptions();
96+
const hasFix = f => f.hasFix() && f.getRuleName() === ruleName;
97+
if (failures.some(hasFix)) {
98+
const sourceFile = tslint.getSourceFile(filePath, text);
99+
const fixableFailures = tslint
100+
.removeDisabledFailures(sourceFile, rule.apply(sourceFile))
101+
.filter(f => f.hasFix());
102+
103+
if (fixableFailures.length) {
104+
const fixes = fixableFailures.map(f => f.getFix());
105+
return tslint.Replacement.applyFixes(text, fixes);
106+
}
107+
}
108+
return text;
109+
}, text);
110+
};
98111
}

0 commit comments

Comments
 (0)