Skip to content

Commit

Permalink
feat(tslint): create tslint plugin
Browse files Browse the repository at this point in the history
danger-plugin-tslint runs TSLint on your codebase with your defined `tsconfig.json` and `tslint.json` files. If there are any failures, this plugin will call `fail()` with the formatted output in a message. Otherwise, it will call `message()` saying that TSLint passed. 🆒

Usage example:

```js
// dangerfile.js
import tslint from 'danger-plugin-tslint'

tslint()
```

If you want to supply custom options, you can do that too:

```js
// dangerfile.js
import path from 'path'
import tslint from 'danger-plugin-tslint'

tslint({
  tslintPath: path.resolve(__dirname, 'path/to/tslint.json'),
  tsconfigPath: path.resolve(__dirname, 'path/to/tsconfig.json'),
  formatter: 'stylish',
})
```
  • Loading branch information
macklinu committed May 24, 2017
1 parent e4df88e commit e90a548
Show file tree
Hide file tree
Showing 19 changed files with 996 additions and 45 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ typings/
# dotenv environment variables file
.env

# Generated docs
docs/

# Generated source
dist/

### VisualStudioCode ###
.vscode/*
Expand Down
42 changes: 41 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,43 @@
# danger-plugin-tslint

TODO
[![Build Status](https://travis-ci.org/macklinu/danger-plugin-tslint.svg?branch=master)](https://travis-ci.org/macklinu/danger-plugin-tslint)
[![npm version](https://badge.fury.io/js/danger-plugin-tslint.svg)](https://badge.fury.io/js/danger-plugin-tslint)
[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release)

> [Danger](https://github.com/danger/danger-js) plugin for TSLint

## Usage

Install:

```sh
yarn add danger-plugin-tslint --dev
```

At a glance:

```js
// dangerfile.js
import tslint from 'danger-plugin-tslint'

tslint()
```

See the [documentation](http://macklin.underdown.me/danger-plugin-tslint/modules/tslint.html#tslint-1) for detailed information (and also check out [`src/index.ts`](https://github.com/macklinu/danger-plugin-tslint/blob/master/src/index.ts)).

## Changelog

See the GitHub [release history](https://github.com/macklinu/danger-plugin-tslint/releases).

## Development

Install [Yarn](https://yarnpkg.com/en/), and install the dependencies - `yarn install`.

Run the [Jest](https://facebook.github.io/jest/) test suite with `yarn test`.

This project uses [semantic-release](https://github.com/semantic-release/semantic-release) for automated NPM package publishing.

The main caveat: instead of running `git commit`, run `yarn commit` and follow the prompts to input a conventional changelog message via [commitizen](https://github.com/commitizen/cz-cli).

:heart:
3 changes: 3 additions & 0 deletions __tests__/project-with-lint-errors/add.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
function add(a, b) {
return a + b
}
18 changes: 18 additions & 0 deletions __tests__/project-with-lint-errors/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import * as path from 'path'
import tslint from '../../src'

// tslint:disable:no-string-literal

beforeEach(() => {
global['fail'] = jest.fn()
})

test('Should fail because of missing semicolon', () => {
tslint({
tsconfigPath: path.resolve(__dirname, 'tsconfig.json'),
tslintPath: path.resolve(__dirname, 'tslint.json'),
})
expect(global['fail']).toHaveBeenCalledWith(
expect.stringMatching(/add.ts\[2, 15\]: Missing semicolon/),
)
})
11 changes: 11 additions & 0 deletions __tests__/project-with-lint-errors/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"compilerOptions": {
"lib": ["es5", "es2015"],
"module": "commonjs",
"moduleResolution": "node",
"target": "es5"
},
"files": [
"add.ts"
]
}
5 changes: 5 additions & 0 deletions __tests__/project-with-lint-errors/tslint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"rules": {
"semicolon": [true]
}
}
3 changes: 3 additions & 0 deletions __tests__/project-without-lint-errors/add.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
function add(a, b) {
return a + b;
}
16 changes: 16 additions & 0 deletions __tests__/project-without-lint-errors/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import * as path from 'path'
import tslint from '../../src'

// tslint:disable:no-string-literal

beforeEach(() => {
global['message'] = jest.fn()
})

test('Should pass because there are no lint errors', () => {
tslint({
tsconfigPath: path.resolve(__dirname, 'tsconfig.json'),
tslintPath: path.resolve(__dirname, 'tslint.json'),
})
expect(global['message']).toHaveBeenCalledWith(':white_check_mark: TSLint passed')
})
11 changes: 11 additions & 0 deletions __tests__/project-without-lint-errors/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"compilerOptions": {
"lib": ["es5", "es2015"],
"module": "commonjs",
"moduleResolution": "node",
"target": "es5"
},
"files": [
"add.ts"
]
}
5 changes: 5 additions & 0 deletions __tests__/project-without-lint-errors/tslint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"rules": {
"semicolon": [true]
}
}
1 change: 0 additions & 1 deletion index.test.ts

This file was deleted.

3 changes: 0 additions & 3 deletions index.ts

This file was deleted.

22 changes: 17 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,15 @@
"scripts": {
"precommit": "lint-staged",
"prepush": "npm run test",
"commit": "git-cz",
"commitmsg": "validate-commit-msg",
"lint": "tslint *.ts",
"build": "tsc -p tsconfig.json",
"lint": "tslint '{__tests__/**/*.test.ts,src/**/*.ts}' -c tslint.json -p tsconfig.json",
"test": "jest",
"docs": "typedoc --theme minimal --out docs src/index.ts",
"docs:serve": "npm run docs && serve docs",
"docs:deploy": "npm run docs && node scripts/deploy-docs.js",
"prepublish": "npm run build",
"semantic-release": "semantic-release pre && npm publish && semantic-release post"
},
"repository": {
Expand All @@ -28,26 +34,32 @@
"homepage": "https://github.com/macklinu/danger-plugin-tslint#readme",
"devDependencies": {
"@types/jest": "^19.2.3",
"@types/node": "^7.0.22",
"commitizen": "^2.9.6",
"cz-conventional-changelog": "^2.0.0",
"danger": "^0.19.0",
"gh-pages": "^1.0.0",
"husky": "^0.13.3",
"jest": "^20.0.3",
"lint-staged": "^3.4.2",
"semantic-release": "^6.3.6",
"ts-jest": "^20.0.4",
"tslint": "^5.0.0",
"typedoc": "^0.7.1",
"typedoc-plugin-external-module-name": "^1.0.9",
"typescript": "^2.3.3",
"validate-commit-msg": "^2.12.1"
},
"optionalDependencies": {
"serve": "^5.1.5"
},
"jest": {
"testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.ts$",
"testRegex": ".*\\.test\\.ts$",
"transform": {
".(ts|tsx)": "<rootDir>/node_modules/ts-jest/preprocessor.js"
},
"moduleFileExtensions": [
"ts",

"js"
]
},
Expand All @@ -57,8 +69,8 @@
}
},
"lint-staged": {
"*.ts": [
"tslint --fix",
"{__tests__/**/*.test.ts,src/**/*.ts}": [
"tslint -c tslint.json -p tsconfig.json --fix",
"git add"
]
}
Expand Down
20 changes: 20 additions & 0 deletions scripts/deploy-docs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
'use strict'

const ghpages = require('gh-pages')

const docsDir = 'docs'
const options = {
user: {
name: 'Macklin Underdown',
email: 'macklinu@users.noreply.github.com'
},
}
const callback = (err) => {
if (err) {
console.error(err)
} else {
console.log('🚀 Docs deployed to GitHub Pages')
}
}

ghpages.publish(docsDir, options, callback)
3 changes: 3 additions & 0 deletions src/ambient.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Danger global functions
declare function fail(message: string): void
declare function message(message: string): void
90 changes: 90 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/**
* @module tslint
*/
/**
* This second comment is required until
* https://github.com/christopherthielen/typedoc-plugin-external-module-name/issues/6 is resolved.
*/

import * as fs from 'fs'
import { Configuration, findFormatter, Linter, RuleFailure } from 'tslint'

/**
* Possible TSLint [formatters](https://palantir.github.io/tslint/formatters/).
*/
type Formatter =
| 'checkstyle'
| 'codeFrame'
| 'fileslist'
| 'json'
| 'msbuild'
| 'pmd'
| 'prose'
| 'stylish'
| 'tap'
| 'verbose'
| 'vso'

interface IPluginConfig {
/**
* The path to your project's `tslint.json` file.
* @default `'tslint.json'`
*/
tslintPath?: string
/**
* The path to your project's `tsconfig.json` file.
* @default `'tsconfig.json'`
*/
tsconfigPath?: string
/**
* A TSLint [formatter](https://palantir.github.io/tslint/formatters/).
* @default `'prose'`
*/
formatter?: Formatter
}

/**
* Runs TSLint on a project's source code and reports results to Danger.
* If there are any lint violations, Danger will fail the build and post results in a comment.
* If there are no lint violations, Danger will comment saying that TSLint passed.
*
* @export
* @param config The optional config object.
*/
export default function tslint(config: IPluginConfig = {}): void {
const {
tslintPath = 'tslint.json',
tsconfigPath = 'tsconfig.json',
formatter = 'prose',
} = config

// Set up TSLint
const options = { fix: false }
const program = Linter.createProgram(tsconfigPath)
const linter = new Linter(options, program)
const filenames = Linter.getFileNames(program)

// Lint all files from tsconfig.json
filenames.forEach((fileName) => {
const configuration = Configuration.findConfiguration(tslintPath, fileName).results
const fileContents = fs.readFileSync(fileName, 'utf8')
linter.lint(fileName, fileContents, configuration)
})

// Handle lint output
const { failures } = linter.getResult()
if (failures.length > 0) {
const formattedFailures = _getFormattedFailures(formatter, failures)
fail(formattedFailures)
} else {
message(':white_check_mark: TSLint passed')
}
}

function _getFormattedFailures(formatter: Formatter, failures: RuleFailure[]): string {
const Formatter = findFormatter(formatter)
if (Formatter !== undefined) {
return new Formatter().format(failures)
}
throw new Error(`formatter '${formatter}' not found`)
}
25 changes: 21 additions & 4 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,26 @@
{
"compilerOptions": {
"lib": ["es5", "es2015"],
"module": "commonjs",
"moduleResolution": "node",
"noImplicitAny": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"outDir": "dist",
"pretty": true,
"removeComments": true,
"sourceMap": false,
"strictNullChecks": true,
"suppressImplicitAnyIndexErrors": true,
"target": "es5",
"noImplicitAny": false,
"strict": true,
"sourceMap": false
}
"types": ["node"]
},
"include": [
"src/**/*"
],
"exclude": [
"node_modules"
]
}
6 changes: 5 additions & 1 deletion tslint.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
{
"extends": [
"tslint:recommended"
]
],
"rules": {
"semicolon": [true, "never"],
"quotemark": [true, "single"]
}
}
Loading

0 comments on commit e90a548

Please sign in to comment.