Skip to content

Commit

Permalink
fix: resolve eslint dependencies for proper watching (#602)
Browse files Browse the repository at this point in the history
Closes: #580
  • Loading branch information
piotr-oles committed May 3, 2021
1 parent 13c276f commit 58ef473
Show file tree
Hide file tree
Showing 5 changed files with 4,812 additions and 31 deletions.
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -67,6 +67,7 @@
"cosmiconfig": "^6.0.0",
"deepmerge": "^4.2.2",
"fs-extra": "^9.0.0",
"glob": "^7.1.6",
"memfs": "^3.1.2",
"minimatch": "^3.0.4",
"schema-utils": "2.7.0",
Expand Down
100 changes: 77 additions & 23 deletions src/eslint-reporter/reporter/EsLintReporter.ts
Expand Up @@ -3,54 +3,108 @@ import { CLIEngine, LintReport, LintResult } from '../types/eslint';
import { createIssuesFromEsLintResults } from '../issue/EsLintIssueFactory';
import { EsLintReporterConfiguration } from '../EsLintReporterConfiguration';
import { Reporter } from '../../reporter';
import { normalize } from 'path';
import minimatch from 'minimatch';
import glob from 'glob';

function createEsLintReporter(configuration: EsLintReporterConfiguration): Reporter {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { CLIEngine } = require('eslint');
const engine: CLIEngine = new CLIEngine(configuration.options);

let isInitialRun = true;
let isInitialGetFiles = true;

const lintResults = new Map<string, LintResult>();
const includedFilesPatterns = engine.resolveFileGlobPatterns(configuration.files);
const includedGlobPatterns = engine.resolveFileGlobPatterns(configuration.files);
const includedFiles = new Set<string>();

function isFileIncluded(path: string) {
return (
includedGlobPatterns.some((pattern) => minimatch(path, pattern)) &&
!engine.isPathIgnored(path)
);
}

async function getFiles() {
if (isInitialGetFiles) {
isInitialGetFiles = false;

const resolvedGlobs = await Promise.all(
includedGlobPatterns.map(
(globPattern) =>
new Promise<string[]>((resolve) => {
glob(globPattern, (error, resolvedFiles) => {
if (error) {
// fail silently
resolve([]);
} else {
resolve(resolvedFiles || []);
}
});
})
)
);

for (const resolvedGlob of resolvedGlobs) {
for (const resolvedFile of resolvedGlob) {
if (isFileIncluded(resolvedFile)) {
includedFiles.add(resolvedFile);
}
}
}
}

return Array.from(includedFiles);
}

function getDirs() {
return includedGlobPatterns || [];
}

function getExtensions() {
return configuration.options.extensions || [];
}

return {
getReport: async ({ changedFiles = [], deletedFiles = [] }) => {
return {
async getDependencies() {
for (const changedFile of changedFiles) {
if (isFileIncluded(changedFile)) {
includedFiles.add(changedFile);
}
}
for (const deletedFile of deletedFiles) {
includedFiles.delete(deletedFile);
}

return {
files: [],
dirs: [],
extensions: [],
files: (await getFiles()).map((file) => normalize(file)),
dirs: getDirs().map((dir) => normalize(dir)),
extensions: getExtensions(),
};
},
async getIssues() {
// cleanup old results
changedFiles.forEach((changedFile) => {
for (const changedFile of changedFiles) {
lintResults.delete(changedFile);
});
deletedFiles.forEach((removedFile) => {
lintResults.delete(removedFile);
});
}
for (const deletedFile of deletedFiles) {
lintResults.delete(deletedFile);
}

// get reports
const lintReports: LintReport[] = [];

if (isInitialRun) {
lintReports.push(engine.executeOnFiles(includedFilesPatterns));
lintReports.push(engine.executeOnFiles(includedGlobPatterns));
isInitialRun = false;
} else {
// we need to take care to not lint files that are not included by the configuration.
// the eslint engine will not exclude them automatically
const changedAndIncludedFiles = changedFiles.filter(
(changedFile) =>
includedFilesPatterns.some((includedFilesPattern) =>
minimatch(changedFile, includedFilesPattern)
) &&
(configuration.options.extensions || []).some((extension) =>
changedFile.endsWith(extension)
) &&
!engine.isPathIgnored(changedFile)
const changedAndIncludedFiles = changedFiles.filter((changedFile) =>
isFileIncluded(changedFile)
);

if (changedAndIncludedFiles.length) {
Expand All @@ -64,11 +118,11 @@ function createEsLintReporter(configuration: EsLintReporterConfiguration): Repor
}

// store results
lintReports.forEach((lintReport) => {
lintReport.results.forEach((lintResult) => {
for (const lintReport of lintReports) {
for (const lintResult of lintReport.results) {
lintResults.set(lintResult.filePath, lintResult);
});
});
}
}

// get actual list of previous and current reports
const results = Array.from(lintResults.values());
Expand Down
73 changes: 66 additions & 7 deletions test/e2e/EsLint.spec.ts
Expand Up @@ -48,17 +48,15 @@ describe('EsLint', () => {
// test case for providing absolute path to files
await sandbox.patch(
'webpack.config.js',
"files: './src/**/*'",
"files: path.resolve(__dirname, './src/**/*')"
"files: './src/**/*.{ts,tsx,js,jsx}'",
"files: path.resolve(__dirname, './src/**/*.{ts,tsx,js,jsx}')"
);
}

const driver = createWebpackDevServerDriver(sandbox.spawn('npm run webpack-dev-server'), async);
let errors: string[];

// first compilation contains 2 warnings
errors = await driver.waitForErrors();
expect(errors).toEqual([
expect(await driver.waitForErrors()).toEqual([
[
'WARNING in src/authenticate.ts:14:34',
'@typescript-eslint/no-explicit-any: Unexpected any. Specify a different type.',
Expand Down Expand Up @@ -124,8 +122,7 @@ describe('EsLint', () => {
[' lastName?: string;', '}', '', 'let temporary: any;', ''].join('\n')
);

errors = await driver.waitForErrors();
expect(errors).toEqual([
expect(await driver.waitForErrors()).toEqual([
[
'WARNING in src/model/User.ts:11:5',
"@typescript-eslint/no-unused-vars: 'temporary' is defined but never used.",
Expand All @@ -151,6 +148,68 @@ describe('EsLint', () => {
]);
});

it('adds files dependencies to webpack', async () => {
await sandbox.load([
await readFixture(join(__dirname, 'fixtures/environment/eslint-basic.fixture'), {
FORK_TS_CHECKER_WEBPACK_PLUGIN_VERSION: JSON.stringify(
FORK_TS_CHECKER_WEBPACK_PLUGIN_VERSION
),
TS_LOADER_VERSION: JSON.stringify('^5.0.0'),
TYPESCRIPT_VERSION: JSON.stringify('~3.8.0'),
WEBPACK_VERSION: JSON.stringify('^4.0.0'),
WEBPACK_CLI_VERSION: JSON.stringify(WEBPACK_CLI_VERSION),
WEBPACK_DEV_SERVER_VERSION: JSON.stringify(WEBPACK_DEV_SERVER_VERSION),
ASYNC: JSON.stringify(false),
}),
await readFixture(join(__dirname, 'fixtures/implementation/typescript-basic.fixture')),
]);

// update configuration
await sandbox.patch(
'webpack.config.js',
"files: './src/**/*.{ts,tsx,js,jsx}'",
"files: './outside/**/*.{ts,tsx,js,jsx}'"
);

// create a file with lint error
await sandbox.write('./outside/test.ts', 'const x = 4;');

const driver = createWebpackDevServerDriver(sandbox.spawn('npm run webpack-dev-server'), false);

// initially we should have 1 error
expect(await driver.waitForErrors()).toEqual([
[
'WARNING in outside/test.ts:1:7',
"@typescript-eslint/no-unused-vars: 'x' is assigned a value but never used.",
' > 1 | const x = 4;',
' | ^',
].join('\n'),
]);

// let's fix that error
await sandbox.write('./outside/test.ts', 'export const x = 4;');
await driver.waitForNoErrors();

// add a new file in this directory
await sandbox.write('./outside/another.ts', '');
await driver.waitForNoErrors();

// update another.ts with a code that has 1 lint error
await sandbox.write('./outside/another.ts', 'const y = 5;');
expect(await driver.waitForErrors()).toEqual([
[
'WARNING in outside/another.ts:1:7',
"@typescript-eslint/no-unused-vars: 'y' is assigned a value but never used.",
' > 1 | const y = 5;',
' | ^',
].join('\n'),
]);

// let's remove this file - this will check if we handle remove events
await sandbox.remove('./outside/another.ts');
await driver.waitForNoErrors();
});

it('fixes errors with `fix: true` option', async () => {
await sandbox.load([
await readFixture(join(__dirname, 'fixtures/environment/eslint-basic.fixture'), {
Expand Down
2 changes: 1 addition & 1 deletion test/e2e/fixtures/environment/eslint-basic.fixture
Expand Up @@ -85,7 +85,7 @@ module.exports = {
async: ${ASYNC},
eslint: {
enabled: true,
files: './src/**/*'
files: './src/**/*.{ts,tsx,js,jsx}'
},
logger: {
infrastructure: "console"
Expand Down

0 comments on commit 58ef473

Please sign in to comment.