diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..0de77bf --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,62 @@ +name: Continuous Integration + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + release: + types: [ created ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Setup Node + uses: actions/setup-node@v3 + with: + node-version: 16 + + - name: Install dependencies + run: npm ci + + - name: Run test suite + run: npm run test + + - name: Build + run: npm run build + + + publish: + needs: build + runs-on: ubuntu-latest + if: ${{ github.event_name == 'release' }} + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Setup Node + uses: actions/setup-node@v3 + with: + node-version: 16 + registry-url: https://registry.npmjs.org/ + + - name: Install dependencies + run: npm ci + + - name: Build + run: npm run build + + - name: Set new version + run: npm version --allow-same-version --no-git-tag-version ${{ github.event.release.tag_name }} + + - name: Publish release + run: npm publish + env: + NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fc73c4f --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/.idea/ +/node_modules/ +/package-lock.json diff --git a/README.md b/README.md index 19de2cb..ecd5087 100644 --- a/README.md +++ b/README.md @@ -1 +1,101 @@ -# typescript-gitlab-format \ No newline at end of file +[![Continuous Integration](https://github.com/Moxio/typescript-gitlab-format/actions/workflows/ci.yml/badge.svg)](https://github.com/Moxio/typescript-gitlab-format/actions/workflows/ci.yml) +[![NPM version](https://img.shields.io/npm/v/typescript-gitlab-format.svg)](https://www.npmjs.com/package/typescript-gitlab-format) + +typescript-gitlab-format +=================== +Library for converting output of tsc --noEmit command to the gitlab ci codeclimate format, and filter unwanted entries. + +Installation +------------ +This library can be installed from the NPM package registry. +Depending on your use case it might be better to install this package globally +(if you want to run these commands from your continuous integration server for example) + +Using NPM: +``` +npm install typescript-gitlab-format +``` +or Yarn +``` +yarn add typescript-gitlab-format +``` + +Usage +----- +From the command line you can run this command +``` +./node_modules/.bin/tsc --project ./tsconfig.json --noEmit | ./node_modules/.bin/typescript-gitlab-format -e [exclude regex] > build-logs/typescript-error.json" +``` +There is one optional command line argument: + +| short arg | long arg | effect | +|---|---|---| +| -e | --exclude | the javascript regex to determine which entries get excluded | + +Provided filters +-------------- + +### -e --exclude +Passing along the -e or --exclude flag will exclude entries within the typescript output based on filepaths that match the given regular expression. +A check is performed using standard javascript regexp: `RegExp(..passes argument..).test(..tsc filepath..)`. +If this function returns true the file is skipped. + +#### example +Given tsc output that produces errors in these files: +- `node_modules/example/example.d.ts` +- `example/example.d.ts` + +Running this command: +`./node_modules/.bin/tsc --project ./tsconfig.json --noEmit | ./node_modules/.bin/typescript-gitlab-format -e "node_modules\\/" > build-logs/typescript-error.json"` + +Will result in a file with these rules: +```json +[ + { + "categories": [ + "Compatibility" + ], + "check_name": "TS7006", + "description": " Parameter 'node' implicitly has an 'any' type.\n", + "location": { + "path": "example/example.d.ts", + "positions": { + "begin": { + "column": 22, + "line": 296 + }, + "end": { + "column": 22, + "line": 296 + } + } + }, + "severity": "major", + "type": "issue" + } +] +``` + +Versioning +---------- +This project adheres to [Semantic Versioning](http://semver.org/). + +Contributing +------------ +Contributions to this project are more than welcome. + +License +------- +This project is released under the MIT license. + +Treeware +-------- +This package is [Treeware](https://treeware.earth/). If you use it in production, +then we'd appreciate it if you [**buy the world a tree**](https://plant.treeware.earth/Moxio/typescript-gitlab-format) +to thank us for our work. By contributing to the Treeware forest you'll be creating +employment for local families and restoring wildlife habitats. + +--- +Made with love, coffee and fun by the [Moxio](https://www.moxio.com) team from +Delft, The Netherlands. Interested in joining our awesome team? Check out our +[vacancies](https://werkenbij.moxio.com/) (in Dutch). diff --git a/babel.config.js b/babel.config.js new file mode 100644 index 0000000..68e3554 --- /dev/null +++ b/babel.config.js @@ -0,0 +1,7 @@ +// babel.config.js +module.exports = { + presets: [ + ['@babel/preset-env', {targets: {node: 'current'}}], + '@babel/preset-typescript', + ], +}; \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..c0f3cc3 --- /dev/null +++ b/package.json @@ -0,0 +1,45 @@ +{ + "name": "typescript-gitlab-format", + "version": "1.0.0", + "description": "Convert and filter tsc output into gitlab codeclimate format", + "keywords": [ + "gitlab", + "tsc", + "typescript", + "format" + ], + "scripts": { + "build": "tsc", + "test": "jest" + }, + "bin": "dist/cli.js", + "files": [ + "dist/" + ], + "author": { + "name": "Moxio", + "email": "info@moxio.com", + "url": "https://www.moxio.com" + }, + "repository": { + "type": "git", + "url": "https://github.com/Moxio/typescript-gitlab-format.git" + }, + "license": "MIT", + "devDependencies": { + "@babel/core": "^7.15.5", + "@babel/preset-env": "^7.15.6", + "@babel/preset-typescript": "^7.15.0", + "@types/jest": "^27.0.1", + "babel-jest": "^27.2.0", + "jest": "^27.2.0", + "ts-node": "^10.2.1" + }, + "dependencies": { + "commander": "^8.2.0", + "typescript": "^4.4.3", + "@aivenio/tsc-output-parser": "^2.1.1", + "codeclimate-types": "^0.3.1", + "get-stdin": "^9.0.0" + } +} diff --git a/src/cli.ts b/src/cli.ts new file mode 100644 index 0000000..2349ad2 --- /dev/null +++ b/src/cli.ts @@ -0,0 +1,25 @@ +#!/usr/bin/env node + +import { Command } from "commander"; +import getStdin from "get-stdin"; +import format from "./format"; +import filter from "./filter"; + +const program = new Command(); + +program + .option("-e --exclude ", "paths to exclude, default js regex"); + + +program.parse(process.argv); +const options = program.opts(); + +getStdin().then((stdIn) => { + let output = format(stdIn); + + if (options.exclude) { + output = filter(output, new RegExp(options.exclude)); + } + + console.log(output); +}); diff --git a/src/filter.ts b/src/filter.ts new file mode 100644 index 0000000..83ff230 --- /dev/null +++ b/src/filter.ts @@ -0,0 +1,7 @@ +import { Issue } from "codeclimate-types"; + +export default function filter (input: Issue[], excludePattern: RegExp): Issue[] { + return input.filter((issue) => { + return excludePattern.test(issue.location.path) === false; + }); +} diff --git a/src/format.ts b/src/format.ts new file mode 100644 index 0000000..9f66e78 --- /dev/null +++ b/src/format.ts @@ -0,0 +1,29 @@ +import { parse, GrammarItem } from '@aivenio/tsc-output-parser'; +import { Issue } from 'codeclimate-types'; + +export default function format (input: string): Issue[] { + const foo: GrammarItem[] = parse(input); + + return foo.map((inputItem) => { + return { + type: 'issue', + categories: [ "Compatibility" ], + check_name: inputItem.value.tsError.value.errorString, + description: inputItem.value.message.value, + severity: 'major', + location: { + path: inputItem.value.path.value, + positions: { + begin: { + line: inputItem.value.cursor.value.line, + column: inputItem.value.cursor.value.col + }, + end: { + line: inputItem.value.cursor.value.line, + column: inputItem.value.cursor.value.col + }, + } + } + } + }); +} diff --git a/test/filter.test.ts b/test/filter.test.ts new file mode 100644 index 0000000..948ae62 --- /dev/null +++ b/test/filter.test.ts @@ -0,0 +1,76 @@ +import filter from "../src/filter"; + +describe("filter", () => { + it("filters output", () => { + const output = filter([ + { + "categories": [ + "Compatibility" + ], + "check_name": "TS7006", + "description": " Parameter 'node' implicitly has an 'any' type.\n", + "location": { + "path": "node_modules/example/example.d.ts", + "positions": { + "begin": { + "column": 22, + "line": 296 + }, + "end": { + "column": 22, + "line": 296 + } + } + }, + "severity": "major", + "type": "issue" + }, + { + "categories": [ + "Compatibility" + ], + "check_name": "TS7006", + "description": " Parameter 'node' implicitly has an 'any' type.\n", + "location": { + "path": "example/example.d.ts", + "positions": { + "begin": { + "column": 22, + "line": 296 + }, + "end": { + "column": 22, + "line": 296 + } + } + }, + "severity": "major", + "type": "issue" + } + ], new RegExp("node_modules\\/")); + expect(output).toEqual([ + { + "categories": [ + "Compatibility" + ], + "check_name": "TS7006", + "description": " Parameter 'node' implicitly has an 'any' type.\n", + "location": { + "path": "example/example.d.ts", + "positions": { + "begin": { + "column": 22, + "line": 296 + }, + "end": { + "column": 22, + "line": 296 + } + } + }, + "severity": "major", + "type": "issue" + } + ]); + }) +}); diff --git a/test/format.test.ts b/test/format.test.ts new file mode 100644 index 0000000..5cde38e --- /dev/null +++ b/test/format.test.ts @@ -0,0 +1,53 @@ +import format from "../src/format"; + +describe("format", () => { + it("formats tsc output to json", () => { + const output = format("node_modules/example/example.d.ts(296,22): error TS7006: Parameter 'node' implicitly has an 'any' type.\nexample/example.d.ts(296,22): error TS7006: Parameter 'node' implicitly has an 'any' type.\n"); + expect(output).toEqual([ + { + "categories": [ + "Compatibility" + ], + "check_name": "TS7006", + "description": " Parameter 'node' implicitly has an 'any' type.\n", + "location": { + "path": "node_modules/example/example.d.ts", + "positions": { + "begin": { + "column": 22, + "line": 296 + }, + "end": { + "column": 22, + "line": 296 + } + } + }, + "severity": "major", + "type": "issue" + }, + { + "categories": [ + "Compatibility" + ], + "check_name": "TS7006", + "description": " Parameter 'node' implicitly has an 'any' type.\n", + "location": { + "path": "example/example.d.ts", + "positions": { + "begin": { + "column": 22, + "line": 296 + }, + "end": { + "column": 22, + "line": 296 + } + } + }, + "severity": "major", + "type": "issue" + } + ]); + }) +}); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..014ea84 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "moduleResolution": "node", + "rootDir": "src", + "declaration": true, + "strictNullChecks": true, + "skipLibCheck": true, + "outDir": "dist", + "lib": [ + "es2018" + ] + }, + "include": [ + "src/**/*.ts", + ] +} \ No newline at end of file