Skip to content

Commit

Permalink
[tslint] lint typescript code
Browse files Browse the repository at this point in the history
  • Loading branch information
spalger committed May 17, 2018
1 parent bd0f4e1 commit 96ccc5b
Show file tree
Hide file tree
Showing 27 changed files with 590 additions and 48 deletions.
12 changes: 10 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@
"debug": "node --nolazy --inspect --debug-brk scripts/kibana --dev",
"precommit": "node scripts/precommit_hook",
"karma": "karma start",
"lint": "echo 'use `node scripts/eslint`' && false",
"lintroller": "echo 'use `node scripts/eslint --fix`' && false",
"lint": "echo 'use `node scripts/eslint` and/or `node scripts/tslint`' && false",
"lintroller": "echo 'use `node scripts/eslint --fix` and/or `node scripts/tslint --fix`' && false",
"makelogs": "echo 'use `node scripts/makelogs`' && false",
"mocha": "echo 'use `node scripts/mocha`' && false",
"sterilize": "grunt sterilize",
Expand All @@ -85,6 +85,8 @@
"@kbn/pm": "link:packages/kbn-pm",
"@kbn/test-subj-selector": "link:packages/kbn-test-subj-selector",
"@kbn/ui-framework": "link:packages/kbn-ui-framework",
"@types/eslint": "^4.16.2",
"@types/execa": "^0.9.0",
"JSONStream": "1.1.1",
"accept-language-parser": "1.2.0",
"angular": "1.6.9",
Expand Down Expand Up @@ -225,6 +227,8 @@
"@kbn/eslint-plugin-license-header": "link:packages/kbn-eslint-plugin-license-header",
"@kbn/plugin-generator": "link:packages/kbn-plugin-generator",
"@kbn/test": "link:packages/kbn-test",
"@types/getopts": "^2.0.0",
"@types/listr": "^0.13.0",
"@types/minimatch": "^2.0.29",
"angular-mocks": "1.4.7",
"babel-eslint": "8.1.2",
Expand Down Expand Up @@ -281,6 +285,7 @@
"karma-safari-launcher": "1.0.0",
"leadfoot": "1.7.5",
"license-checker": "^16.0.0",
"listr": "^0.14.1",
"load-grunt-config": "0.19.2",
"makelogs": "^4.0.5",
"marked-text-renderer": "0.1.0",
Expand All @@ -303,6 +308,9 @@
"ts-jest": "^22.4.6",
"ts-loader": "^3.5.0",
"ts-node": "^6.0.3",
"tslint": "^5.10.0",
"tslint-config-prettier": "^1.12.0",
"tslint-plugin-prettier": "^1.3.0",
"typescript": "^2.8.3",
"vinyl-fs": "^3.0.2",
"xml2js": "^0.4.19",
Expand Down
18 changes: 18 additions & 0 deletions packages/kbn-dev-utils/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Readable } from 'stream';

type LogLevel = 'silent' | 'error' | 'warning' | 'info' | 'debug' | 'verbose';

export class ToolingLog extends Readable {
public verbose(...args: any[]): void;
public debug(...args: any[]): void;
public info(...args: any[]): void;
public success(...args: any[]): void;
public warning(...args: any[]): void;
public error(errOrMsg: string | Error): void;
public write(...args: any[]): void;
public indent(spaces: number): void;
public getLevel(): LogLevel;
public setLevel(level: LogLevel): void;
}

export function createToolingLog(level?: LogLevel): ToolingLog;
6 changes: 6 additions & 0 deletions packages/kbn-dev-utils/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"extends": "../../tsconfig.json",
"include": [
"index.d.ts"
],
}
18 changes: 18 additions & 0 deletions packages/kbn-pm/tslint.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
extends: ../../tslint.yaml

rules:
max-classes-per-file: false
interface-name: false
variable-name: false
no-empty: false
object-literal-sort-keys: false
member-ordering: false
no-console: false
only-arrow-functions: false
no-shadowed-variable: false
no-empty-interface: false
ordered-imports: false
interface-over-type-literal: false
prettier: false
prefer-const: false
member-access: false
14 changes: 14 additions & 0 deletions packages/kbn-system-loader/tslint.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
extends: ../../tslint.yaml

rules:
max-classes-per-file: false
interface-name: false
variable-name: false
no-empty: false
object-literal-sort-keys: false
member-ordering: false
member-access: false
ordered-imports: false
interface-over-type-literal: false
array-type: false
prefer-const: false
2 changes: 2 additions & 0 deletions scripts/tslint.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
require('../src/babel-register');
require('../src/dev/tslint').runTslintCli();
4 changes: 2 additions & 2 deletions src/dev/eslint/lint_files.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ export function lintFiles(log, files) {
if (report.warningCount > 0) failTypes.push('warning');

if (!failTypes.length) {
log.success('%d files linted successfully', files.length);
log.success('[eslint] %d files linted successfully', files.length);
return;
}

log.error(cli.getFormatter()(report.results));
throw createFailError(`eslint ${failTypes.join(' & ')}`, 1);
throw createFailError(`[eslint] ${failTypes.join(' & ')}`, 1);
}
4 changes: 2 additions & 2 deletions src/dev/eslint/pick_files_to_lint.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ export function pickFilesToLint(log, files) {
const path = file.getRelativePath();

if (cli.isPathIgnored(path)) {
log.warning(`%j ignored by .eslintignore`, file);
log.warning(`[eslint] %j ignored by .eslintignore`, file);
return false;
}

log.debug('linting %j', file);
log.debug('[eslint] linting %j', file);
return true;
});
}
18 changes: 14 additions & 4 deletions src/dev/jest/ts_transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { getCacheKey, install, process } from 'ts-jest';
import { JestConfig, TransformOptions } from 'ts-jest/dist/jest-types';

import { transform } from 'typescript';
import { findProjectForAbsolutePath } from '../typescript';
import { getTsProjectForAbsolutePath } from '../typescript';

function extendJestConfigJSON(jestConfigJSON: string, filePath: string) {
const jestConfig = JSON.parse(jestConfigJSON) as JestConfig;
Expand All @@ -15,20 +15,30 @@ function extendJestConfig(jestConfig: JestConfig, filePath: string) {
globals: {
...(jestConfig.globals || {}),
'ts-jest': {
tsConfigFile: findProjectForAbsolutePath(filePath).getTsConfigPath(),
skipBabel: true,
tsConfigFile: getTsProjectForAbsolutePath(filePath).getTsConfigPath(),
},
},
};
}

module.exports = {
process(src: string, filePath: string, jestConfig: JestConfig, transformOptions: TransformOptions) {
process(
src: string,
filePath: string,
jestConfig: JestConfig,
transformOptions: TransformOptions
) {
const extendedConfig = extendJestConfig(jestConfig, filePath);
return process(src, filePath, extendedConfig, transformOptions);
},

getCacheKey(src: string, filePath: string, jestConfigJSON: string, transformOptions: TransformOptions) {
getCacheKey(
src: string,
filePath: string,
jestConfigJSON: string,
transformOptions: TransformOptions
) {
const extendedConfigJSON = extendJestConfigJSON(jestConfigJSON, filePath);
return getCacheKey(src, filePath, extendedConfigJSON, transformOptions);
},
Expand Down
1 change: 1 addition & 0 deletions src/dev/precommit_hook/casing_check_config.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export const IGNORE_FILE_GLOBS = [
'**/.*',
'**/{webpackShims,__mocks__}/**/*',
'x-pack/docs/**/*',
'src/dev/tslint/rules/*',
];


Expand Down
1 change: 1 addition & 0 deletions src/dev/run/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export function createFailError(msg: string, exitCode: number): Error;
11 changes: 9 additions & 2 deletions src/dev/run_precommit_hook.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
import { run } from './run';

import { lintFiles, pickFilesToLint } from './eslint';
import * as Eslint from './eslint';
import * as Tslint from './tslint';
import { getFilesForCommit, checkFileCasing } from './precommit_hook';

run(async ({ log }) => {
const files = await getFilesForCommit();
await checkFileCasing(log, files);
await lintFiles(log, pickFilesToLint(log, files));

for (const Linter of [Eslint, Tslint]) {
const filesToLint = Linter.pickFilesToLint(log, files);
if (filesToLint.length > 0) {
await Linter.lintFiles(log, filesToLint);
}
}
});
3 changes: 3 additions & 0 deletions src/dev/tslint/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { runTslintCli } from './run_tslint_cli';
export { lintFiles } from './lint_files';
export { pickFilesToLint } from './pick_files_to_lint';
68 changes: 68 additions & 0 deletions src/dev/tslint/lint_files.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { CLIEngine } from 'eslint';
import { run } from 'tslint/lib/runner';

import { ToolingLog } from '@kbn/dev-utils';
import { REPO_ROOT } from '../constants';
import { File } from '../file';
import { createFailError } from '../run';
import { getTsProjectForAbsolutePath } from '../typescript';

function groupFilesByProject(files: File[]) {
const filesByProject: Map<
ReturnType<typeof getTsProjectForAbsolutePath>,
File[]
> = new Map();

files.forEach(file => {
const project = getTsProjectForAbsolutePath(file.getAbsolutePath());
const filesForProject = filesByProject.get(project);

if (!filesForProject) {
filesByProject.set(project, [file]);
} else {
filesForProject.push(file);
}
});

return filesByProject;
}

/**
* Lints a list of files with eslint. eslint reports are written to the log
* and a FailError is thrown when linting errors occur.
*
* @param {ToolingLog} log
* @param {Array<File>} files
* @return {undefined}
*/
export async function lintFiles(log: ToolingLog, files: File[]) {
for (const [project, filesInProject] of groupFilesByProject(files)) {
const exitCode = await run(
{
exclude: [],
files: filesInProject.map(f => f.getAbsolutePath()),
fix: false,
format: 'stylish',
project: project.getTsConfigPath(),
},
{
log(m: string) {
log.write(m);
},
error(m: string) {
log.error(m);
},
}
);

if (exitCode > 0) {
throw createFailError(`[tslint] failure`, 1);
} else {
log.success(
'[tslint/%s] %d files linted successfully',
project.getName(),
files.length
);
}
}
}
7 changes: 7 additions & 0 deletions src/dev/tslint/pick_files_to_lint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { ToolingLog } from '@kbn/dev-utils';

import { File } from '../file';

export function pickFilesToLint(log: ToolingLog, files: File[]) {
return files.filter(file => file.isTypescript());
}
29 changes: 29 additions & 0 deletions src/dev/tslint/rules/requireLicenseHeaderRule.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
const Lint = require('tslint');

const FAILURE_STRING = 'File must start with a license header';
const RULE_NAME = 'require-license-header';

exports.Rule = class extends Lint.Rules.AbstractRule {
apply(sourceFile) {
const [headerText] = this.getOptions().ruleArguments;

if (!headerText) {
throw new Error(`${RULE_NAME} requires a single argument containing the header text`);
}

if (sourceFile.text.startsWith(headerText)) {
return [];
}

return [
new Lint.RuleFailure(
sourceFile,
0,
0,
FAILURE_STRING,
RULE_NAME,
new Lint.Replacement(0, 0, `${headerText}\n\n`)
)
];
}
};
69 changes: 69 additions & 0 deletions src/dev/tslint/run_tslint_cli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import chalk from 'chalk';
import { readFileSync } from 'fs';
import { extname, join, resolve } from 'path';

import { createToolingLog } from '@kbn/dev-utils';
import execa from 'execa';
import getopts from 'getopts';
import Listr from 'listr';

import { PROJECTS } from '../typescript';

class LintFailure {
constructor(
public project: typeof PROJECTS[0],
public error: execa.ExecaError
) {}
}

export function runTslintCli() {
const log = createToolingLog('info');
log.pipe(process.stdout);

const opts = getopts(process.argv.slice(2));

if (!opts.format) {
process.argv.push('--format', 'stylish');
}

if (opts.project) {
require('tslint/bin/tslint');
} else {
const tasks = PROJECTS.map(project => ({
task: () =>
execa(
'tslint',
[...process.argv.slice(2), '--project', project.getTsConfigPath()],
{
cwd: project.getDirectory(),
env: chalk.enabled ? { FORCE_COLOR: 'true' } : {},
stdio: ['ignore', 'pipe', 'pipe'],
}
).catch(error => {
throw new LintFailure(project, error);
}),
title: project.getName(),
}));

const list = new Listr(tasks as any, {
exitOnError: false,
});

list.run().catch((error: any) => {
if (!error.errors) {
log.error('Unhandled execption!');
log.error(error);
process.exit(1);
}

for (const e of error.errors) {
if (e instanceof LintFailure) {
log.write('');
log.error(`${e.project.getName()} failed\n${e.error.stdout}`);
} else {
log.error(e);
}
}
});
}
}
Loading

0 comments on commit 96ccc5b

Please sign in to comment.