diff --git a/.eslintrc.yml b/.eslintrc.yml index 8e73e20..388e751 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -1,20 +1,22 @@ +plugins: +- '@typescript-eslint' + +parser: '@typescript-eslint/parser' + +parserOptions: + sourceType: module + env: - commonjs: true es6: true node: true -extends: 'eslint:recommended' -globals: - Atomics: readonly - SharedArrayBuffer: readonly -parserOptions: - ecmaVersion: 2018 + overrides: - files: - 'regression-tests/*.js' env: jest: true -rules: +rules: # possible errors for-direction: error getter-return: error diff --git a/.gitignore b/.gitignore index 32fd842..7efd11c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ node_modules .vscode/ -regression-tests/test.log coverage +lib diff --git a/package-lock.json b/package-lock.json index 686be7b..a42eec1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -974,6 +974,12 @@ "@types/istanbul-lib-report": "*" } }, + "@types/json-schema": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz", + "integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==", + "dev": true + }, "@types/minimist": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.0.tgz", @@ -1025,6 +1031,130 @@ "integrity": "sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==", "dev": true }, + "@typescript-eslint/eslint-plugin": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.4.0.tgz", + "integrity": "sha512-RVt5wU9H/2H+N/ZrCasTXdGbUTkbf7Hfi9eLiA8vPQkzUJ/bLDCC3CsoZioPrNcnoyN8r0gT153dC++A4hKBQQ==", + "dev": true, + "requires": { + "@typescript-eslint/experimental-utils": "4.4.0", + "@typescript-eslint/scope-manager": "4.4.0", + "debug": "^4.1.1", + "functional-red-black-tree": "^1.0.1", + "regexpp": "^3.0.0", + "semver": "^7.3.2", + "tsutils": "^3.17.1" + }, + "dependencies": { + "regexpp": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", + "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", + "dev": true + }, + "semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", + "dev": true + } + } + }, + "@typescript-eslint/experimental-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.4.0.tgz", + "integrity": "sha512-01+OtK/oWeSJTjQcyzDztfLF1YjvKpLFo+JZmurK/qjSRcyObpIecJ4rckDoRCSh5Etw+jKfdSzVEHevh9gJ1w==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.3", + "@typescript-eslint/scope-manager": "4.4.0", + "@typescript-eslint/types": "4.4.0", + "@typescript-eslint/typescript-estree": "4.4.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^2.0.0" + }, + "dependencies": { + "eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + } + } + }, + "@typescript-eslint/parser": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.4.0.tgz", + "integrity": "sha512-yc14iEItCxoGb7W4Nx30FlTyGpU9r+j+n1LUK/exlq2eJeFxczrz/xFRZUk2f6yzWfK+pr1DOTyQnmDkcC4TnA==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "4.4.0", + "@typescript-eslint/types": "4.4.0", + "@typescript-eslint/typescript-estree": "4.4.0", + "debug": "^4.1.1" + } + }, + "@typescript-eslint/scope-manager": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.4.0.tgz", + "integrity": "sha512-r2FIeeU1lmW4K3CxgOAt8djI5c6Q/5ULAgdVo9AF3hPMpu0B14WznBAtxrmB/qFVbVIB6fSx2a+EVXuhSVMEyA==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.4.0", + "@typescript-eslint/visitor-keys": "4.4.0" + } + }, + "@typescript-eslint/types": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.4.0.tgz", + "integrity": "sha512-nU0VUpzanFw3jjX+50OTQy6MehVvf8pkqFcURPAE06xFNFenMj1GPEI6IESvp7UOHAnq+n/brMirZdR+7rCrlA==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.4.0.tgz", + "integrity": "sha512-Fh85feshKXwki4nZ1uhCJHmqKJqCMba+8ZicQIhNi5d5jSQFteWiGeF96DTjO8br7fn+prTP+t3Cz/a/3yOKqw==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.4.0", + "@typescript-eslint/visitor-keys": "4.4.0", + "debug": "^4.1.1", + "globby": "^11.0.1", + "is-glob": "^4.0.1", + "lodash": "^4.17.15", + "semver": "^7.3.2", + "tsutils": "^3.17.1" + }, + "dependencies": { + "semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", + "dev": true + } + } + }, + "@typescript-eslint/visitor-keys": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.4.0.tgz", + "integrity": "sha512-oBWeroUZCVsHLiWRdcTXJB7s1nB3taFY8WGvS23tiAlT6jXVvsdAV4rs581bgdEjOhn43q6ro7NkOiLKu6kFqA==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.4.0", + "eslint-visitor-keys": "^2.0.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz", + "integrity": "sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==", + "dev": true + } + } + }, "JSONStream": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", @@ -1153,6 +1283,12 @@ "integrity": "sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4=", "dev": true }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, "array-unique": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", @@ -1913,6 +2049,15 @@ "integrity": "sha512-5j5vdRcw3CNctePNYN0Wy2e/JbWT6cAYnXv5OuqPhDpyCGc0uLu2TK0zOCJWNB9kOIfYMSpIulRaDgIi4HJ6Ig==", "dev": true }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "requires": { + "path-type": "^4.0.0" + } + }, "doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -2699,6 +2844,28 @@ } } }, + "globby": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.1.tgz", + "integrity": "sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.1.1", + "ignore": "^5.1.4", + "merge2": "^1.3.0", + "slash": "^3.0.0" + }, + "dependencies": { + "ignore": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "dev": true + } + } + }, "graceful-fs": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", @@ -5781,6 +5948,15 @@ "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==", "dev": true }, + "tsutils": { + "version": "3.17.1", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz", + "integrity": "sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -5826,6 +6002,12 @@ "is-typedarray": "^1.0.0" } }, + "typescript": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.3.tgz", + "integrity": "sha512-tEu6DGxGgRJPb/mVPIZ48e69xCn2yRmCgYmDugAVwmJ6o+0u1RI18eO7E7WBTLYLaEVVOhwQmcdhQHweux/WPg==", + "dev": true + }, "union-value": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", diff --git a/package.json b/package.json index 420d00f..eb7fbfc 100644 --- a/package.json +++ b/package.json @@ -1,35 +1,25 @@ { "name": "tekton-lint", "files": [ - "utils.js", - "watch.js", - "lint.js", - "reporter.js", - "rules.js", - "collect-resources.js", - "Collector.js", - "runner.js", - "rules/*.js", - "rule-loader.js", - "walk.js", - ".tektonlintrc.yaml", - "log-problems.js", - "formatters/*.js" + "lib/**/*.js", + "lib/**/*.d.ts", + ".tektonlintrc.yaml" ], "scripts": { - "lint": "eslint './*.js' './rules/*.js' './regression-tests/*.js'", + "lint": "eslint --ext ts src", "test": "jest", + "build": "tsc", "coverage": "jest --coverage" }, "version": "0.4.4", "description": "Linter for Tekton definitions", - "main": "runner.js", + "main": "lib/runner.js", "repository": { "type": "git", "url": "git@github.com:IBM/tekton-lint.git" }, "bin": { - "tekton-lint": "lint.js" + "tekton-lint": "lib/lint.js" }, "author": "Bence Dányi ", "license": "Apache-2.0", @@ -46,9 +36,12 @@ "devDependencies": { "@commitlint/cli": "^11.0.0", "@commitlint/config-conventional": "^11.0.0", + "@typescript-eslint/eslint-plugin": "^4.4.0", + "@typescript-eslint/parser": "^4.4.0", "eslint": "^6.8.0", "husky": "^4.2.3", - "jest": "^26.0.1" + "jest": "^26.0.1", + "typescript": "^4.0.3" }, "engines": { "node": ">= 12.0.0" diff --git a/readme.markdown b/readme.markdown index 4812900..3d762fc 100644 --- a/readme.markdown +++ b/readme.markdown @@ -120,7 +120,7 @@ interface Problem { ##### Example ```js -const linter = require('tekton-lint/runner'); +const linter = require('tekton-lint'); const problems = await linter(['path/to/defs/**/*.yaml']); diff --git a/regression-tests/regresion.test.js b/regression-tests/regresion.test.js index 019062c..f7a2a9d 100644 --- a/regression-tests/regresion.test.js +++ b/regression-tests/regresion.test.js @@ -1,5 +1,5 @@ -const collect = require('../Collector'); -const runner = require('../runner'); +const collect = require('../lib/Collector').default; +const { default: runner, lint } = require('../lib/runner'); it('regression tests with location', async () => { const result = await runner(['./regression-tests/*.yaml']); @@ -19,6 +19,6 @@ it('regression tests with location', async () => { it('regression tests without location', async () => { const reference = await runner(['./regression-tests/*.yaml']); const docs = await collect(['./regression-tests/*.yaml']); - const result = await runner.lint(docs.map(d => d.content)); + const result = await lint(docs.map(d => d.content)); expect(result).toHaveLength(reference.length); }); diff --git a/rule-loader.js b/rule-loader.js deleted file mode 100644 index b352089..0000000 --- a/rule-loader.js +++ /dev/null @@ -1,66 +0,0 @@ -/* eslint-disable global-require */ -const rules = { - 'no-resourceversion': require('./rules/no-resourceversion.js'), - - // no-duplicate-env - 'no-duplicate-env': require('./rules/no-duplicate-env.js'), - - // no-duplicate-param - 'no-duplicate-param': require('./rules/no-duplicate-param.js'), - - // no-duplicate-resource - 'no-duplicate-resource': require('./rules/no-duplicate-resource.js'), - - // no-extra-param - 'no-extra-param': require('./rules/no-extra-param.js'), - - // no-invalid-dag - 'no-pipeline-task-cycle': require('./rules/no-pipeline-task-cycle.js'), - - // no-invalid-name - 'no-invalid-name': require('./rules/no-invalid-name.js'), - - // no-invalid-param-type - 'no-binding-missing-params': require('./rules/no-binding-missing-params.js'), - 'no-wrong-param-type': require('./rules/no-wrong-param-type.js'), - 'no-pipeline-task-missing-params': require('./rules/no-pipeline-task-missing-params.js'), - - // no-latest-image - 'no-latest-image': require('./rules/no-latest-image.js'), - - // no-missing-param - 'no-missing-param': require('./rules/no-missing-param.js'), - - // no-missing-resource - 'no-missing-resource': require('./rules/no-missing-resource.js'), - 'no-pipeline-missing-task': require('./rules/no-pipeline-missing-task.js'), // TODO: split -> no-invalid-dag - - // no-missing-workspace - 'no-missing-workspace': require('./rules/no-missing-workspace.js'), - - // no-undefined-param - 'no-undefined-param': require('./rules/no-undefined-param.js'), - - // no-undefined-result - 'no-undefined-result': require('./rules/no-undefined-result.js'), - - // no-undefined-volume - 'no-undefined-volume': require('./rules/no-undefined-volume.js'), - - // no-unused-param - 'no-unused-param': require('./rules/no-unused-param.js'), - - // prefer-beta - 'prefer-beta': require('./rules/prefer-beta.js'), - - // prefer-kebab-case - 'prefer-kebab-case': require('./rules/prefer-kebab-case.js'), - - // prefer-when-expression - 'prefer-when-expression': require('./rules/prefer-when-expression.js'), - - // no-deprecated-resource - 'no-deprecated-resource': require('./rules/no-deprecated-resource.js'), -}; - -module.exports = rules; diff --git a/Collector.js b/src/Collector.ts similarity index 76% rename from Collector.js rename to src/Collector.ts index 3c6c873..ec58760 100644 --- a/Collector.js +++ b/src/Collector.ts @@ -1,6 +1,6 @@ -const fs = require('fs'); -const yaml = require('yaml'); -const glob = require('fast-glob'); +import fs from 'fs'; +import yaml from 'yaml'; +import glob from 'fast-glob'; const collector = async (paths) => { const docs = []; @@ -21,4 +21,4 @@ const collector = async (paths) => { return docs; }; -module.exports = collector; +export default collector; diff --git a/collect-resources.js b/src/collect-resources.ts similarity index 95% rename from collect-resources.js rename to src/collect-resources.ts index f5c3b57..7375f24 100644 --- a/collect-resources.js +++ b/src/collect-resources.ts @@ -1,4 +1,4 @@ -module.exports = (docs) => { +export default (docs) => { const resourceReducer = (obj, item) => { if (!obj[item.kind]) { obj[item.kind] = {}; diff --git a/formatters/json.js b/src/formatters/json.ts similarity index 53% rename from formatters/json.js rename to src/formatters/json.ts index 03836e4..ca47e34 100644 --- a/formatters/json.js +++ b/src/formatters/json.ts @@ -1,3 +1,3 @@ -module.exports = (problems) => { +export default (problems) => { console.log(JSON.stringify(problems)); -} +}; diff --git a/formatters/stylish.js b/src/formatters/stylish.ts similarity index 89% rename from formatters/stylish.js rename to src/formatters/stylish.ts index 1f195fd..b9ef8f2 100644 --- a/formatters/stylish.js +++ b/src/formatters/stylish.ts @@ -1,6 +1,6 @@ -const chalk = require('chalk'); -const stripAnsi = require('strip-ansi'); -const table = require('text-table'); +import chalk from 'chalk'; +import stripAnsi from 'strip-ansi'; +import table from 'text-table'; const pluralize = (word, count) => (count === 1 ? word : `${word}s`); @@ -31,10 +31,10 @@ const messageRow = (obj) => { ]; }; -module.exports = (results) => { +export default (results) => { let output = `\n`; - for (const [path, problems] of Object.entries(groupByPath(results))) { + for (const [path, problems] of Object.entries(groupByPath(results))) { output += `${chalk.underline(path)}\n`; const section = table( diff --git a/formatters/vscode.js b/src/formatters/vscode.ts similarity index 75% rename from formatters/vscode.js rename to src/formatters/vscode.ts index d69f986..25e52ec 100644 --- a/formatters/vscode.js +++ b/src/formatters/vscode.ts @@ -1,6 +1,6 @@ -const chalk = require('chalk'); +import chalk from 'chalk'; -const formatLine = (problem) => { +export const formatLine = (problem) => { const level = problem.level === 'error' ? `${chalk.red(problem.level)} ` : chalk.yellow(problem.level); if (problem.loc) { return `${level} (${problem.loc.startLine},${problem.loc.startColumn},${problem.loc.endLine},${problem.loc.endColumn}): ${problem.message}`; @@ -8,19 +8,17 @@ const formatLine = (problem) => { return `${level}: ${problem.message}`; }; -module.exports.formatLine = formatLine; - -module.exports = (problems) => { +export default (problems) => { const groupByFile = problems.reduce((group, problem) => { group[problem.path] = (group[problem.path] || []).concat(problem); return group; }, {}); - for (const [file, fileProblems] of Object.entries(groupByFile)) { + for (const [file, fileProblems] of Object.entries(groupByFile)) { console.log(`${chalk.bold(file)}:`); for (const problem of fileProblems) { console.log(formatLine(problem)); } console.log('\n'); } -} +}; diff --git a/lint.js b/src/lint.ts similarity index 88% rename from lint.js rename to src/lint.ts index b28b24f..05e8436 100755 --- a/lint.js +++ b/src/lint.ts @@ -1,10 +1,10 @@ #!/usr/bin/env node -const { version } = require('./package.json'); -const minimist = require('minimist'); -const watch = require('./watch'); -const run = require('./runner'); -const logProblems = require('./log-problems'); +import minimist from 'minimist'; +import watch from './watch'; +import run from './runner'; +import logProblems from './log-problems'; +const pkg = require('../package.json'); const argv = minimist(process.argv.slice(2), { boolean: ['watch'], @@ -41,7 +41,7 @@ $ tekton-lint --watch '**/*.yaml' (() => { if (argv.version) { - return console.log(`Version: ${version}`); + return console.log(`Version: ${pkg.version}`); } if (argv.help) { @@ -71,7 +71,7 @@ $ tekton-lint --watch '**/*.yaml' } else { run(argv._) .then((problems) => { - logProblems(argv, problems); + logProblems(argv as any, problems); const hasError = problems.some(p => p.level === 'error'); const warningCount = problems.filter(p => p.level === 'warning').length; diff --git a/log-problems.js b/src/log-problems.ts similarity index 62% rename from log-problems.js rename to src/log-problems.ts index f780533..3bd3359 100644 --- a/log-problems.js +++ b/src/log-problems.ts @@ -1,13 +1,16 @@ -/* eslint-disable global-require */ +import stylish from './formatters/stylish'; +import vscode from './formatters/vscode'; +import json from './formatters/json'; + const formatters = { - stylish: require('./formatters/stylish'), - vscode: require('./formatters/vscode'), - json: require('./formatters/json'), + stylish, + vscode, + json, }; const onlyErrors = problems => problems.filter(problem => problem.level === 'error'); -module.exports = ({ format, quiet }, problems) => { +export default ({ format, quiet }, problems) => { if (!(format in formatters)) { process.exitCode = 1; return console.log(`Formatter "${format}" is not available!`); diff --git a/reporter.js b/src/reporter.ts similarity index 94% rename from reporter.js rename to src/reporter.ts index 163edad..e9537b4 100644 --- a/reporter.js +++ b/src/reporter.ts @@ -67,6 +67,9 @@ function instrument(docs) { } class Reporter { + private m: any; + problems: any[]; + constructor(docs = []) { this.m = instrument(docs); this.problems = []; @@ -80,7 +83,7 @@ class Reporter { this.report(message, node, prop, false); } - report(message, node, prop, isError, rule) { + report(message, node, prop, isError, rule?) { this.problems.push({ message, rule, @@ -90,4 +93,4 @@ class Reporter { } } -module.exports = Reporter; +export default Reporter; diff --git a/src/rule-loader.ts b/src/rule-loader.ts new file mode 100644 index 0000000..4686b84 --- /dev/null +++ b/src/rule-loader.ts @@ -0,0 +1,66 @@ +/* eslint-disable global-require */ +const rules = { + 'no-resourceversion': require('./rules/no-resourceversion').default, + + // no-duplicate-env + 'no-duplicate-env': require('./rules/no-duplicate-env').default, + + // no-duplicate-param + 'no-duplicate-param': require('./rules/no-duplicate-param').default, + + // no-duplicate-resource + 'no-duplicate-resource': require('./rules/no-duplicate-resource').default, + + // no-extra-param + 'no-extra-param': require('./rules/no-extra-param').default, + + // no-invalid-dag + 'no-pipeline-task-cycle': require('./rules/no-pipeline-task-cycle').default, + + // no-invalid-name + 'no-invalid-name': require('./rules/no-invalid-name').default, + + // no-invalid-param-type + 'no-binding-missing-params': require('./rules/no-binding-missing-params').default, + 'no-wrong-param-type': require('./rules/no-wrong-param-type').default, + 'no-pipeline-task-missing-params': require('./rules/no-pipeline-task-missing-params').default, + + // no-latest-image + 'no-latest-image': require('./rules/no-latest-image').default, + + // no-missing-param + 'no-missing-param': require('./rules/no-missing-param').default, + + // no-missing-resource + 'no-missing-resource': require('./rules/no-missing-resource').default, + 'no-pipeline-missing-task': require('./rules/no-pipeline-missing-task').default, // TODO: split -> no-invalid-dag + + // no-missing-workspace + 'no-missing-workspace': require('./rules/no-missing-workspace').default, + + // no-undefined-param + 'no-undefined-param': require('./rules/no-undefined-param').default, + + // no-undefined-result + 'no-undefined-result': require('./rules/no-undefined-result').default, + + // no-undefined-volume + 'no-undefined-volume': require('./rules/no-undefined-volume').default, + + // no-unused-param + 'no-unused-param': require('./rules/no-unused-param').default, + + // prefer-beta + 'prefer-beta': require('./rules/prefer-beta').default, + + // prefer-kebab-case + 'prefer-kebab-case': require('./rules/prefer-kebab-case').default, + + // prefer-when-expression + 'prefer-when-expression': require('./rules/prefer-when-expression').default, + + // no-deprecated-resource + 'no-deprecated-resource': require('./rules/no-deprecated-resource').default, +}; + +export default rules; diff --git a/rules.js b/src/rules.ts similarity index 91% rename from rules.js rename to src/rules.ts index cbfeeb4..3bce2a0 100644 --- a/rules.js +++ b/src/rules.ts @@ -1,5 +1,5 @@ -const rules = require('./rule-loader'); -const Reporter = require('./reporter'); +import rules from './rule-loader'; +import Reporter from './reporter'; const createReporter = (rule, config, reporter) => { const isError = config.rules[rule] && config.rules[rule] === 'error'; @@ -36,7 +36,7 @@ const parse = docs => ({ ])), }); -module.exports.lint = function lint(docs, reporter, config) { +export function lint(docs, reporter, config) { reporter = reporter || new Reporter(); config = config || { rules: {}, diff --git a/rules/no-binding-missing-params.js b/src/rules/no-binding-missing-params.ts similarity index 71% rename from rules/no-binding-missing-params.js rename to src/rules/no-binding-missing-params.ts index 6e2aee9..612a5c8 100644 --- a/rules/no-binding-missing-params.js +++ b/src/rules/no-binding-missing-params.ts @@ -1,5 +1,5 @@ -module.exports = (docs, tekton, report) => { - for (const triggerBinding of Object.values(tekton.triggerBindings)) { +export default (docs, tekton, report) => { + for (const triggerBinding of Object.values(tekton.triggerBindings)) { if (!triggerBinding.spec || !triggerBinding.spec.params) continue; for (const param of triggerBinding.spec.params) { if (param.value === undefined) report(`TriggerBinding '${triggerBinding.metadata.name}' defines parameter '${param.name}' with missing value`, param); diff --git a/rules/no-deprecated-resource.js b/src/rules/no-deprecated-resource.ts similarity index 88% rename from rules/no-deprecated-resource.js rename to src/rules/no-deprecated-resource.ts index 8bb2e51..e36a0d9 100644 --- a/rules/no-deprecated-resource.js +++ b/src/rules/no-deprecated-resource.ts @@ -1,4 +1,4 @@ -module.exports = (docs, tekton, report) => { +export default (docs, tekton, report) => { for (const resources of Object.values(tekton)) { for (const resource of Object.values(resources)) { const labels = resource.metadata.labels; diff --git a/rules/no-duplicate-env.js b/src/rules/no-duplicate-env.ts similarity index 80% rename from rules/no-duplicate-env.js rename to src/rules/no-duplicate-env.ts index c25687e..9bd0664 100644 --- a/rules/no-duplicate-env.js +++ b/src/rules/no-duplicate-env.ts @@ -1,5 +1,5 @@ -module.exports = (docs, tekton, report) => { - for (const task of Object.values(tekton.tasks)) { +export default (docs, tekton, report) => { + for (const task of Object.values(tekton.tasks)) { for (const step of task.spec.steps) { if (!step.env) continue; const envVariables = new Set(); diff --git a/rules/no-duplicate-param.js b/src/rules/no-duplicate-param.ts similarity index 78% rename from rules/no-duplicate-param.js rename to src/rules/no-duplicate-param.ts index 8eac94e..0eaab4c 100644 --- a/rules/no-duplicate-param.js +++ b/src/rules/no-duplicate-param.ts @@ -15,21 +15,21 @@ function checkParams(params, report) { } } -module.exports = (docs, tekton, report) => { +export default (docs, tekton, report) => { for (const t of ['triggerBindings', 'pipelines', 'tasks', 'triggerTemplates']) { - for (const crd of Object.values(tekton[t])) { + for (const crd of Object.values(tekton[t])) { checkParams(getParams(crd.kind, crd.spec), report); } } - for (const template of Object.values(tekton.triggerTemplates)) { + for (const template of Object.values(tekton.triggerTemplates)) { for (const crd of template.spec.resourcetemplates) { if (crd.kind !== 'PipelineRun') continue; checkParams(getParams(crd.kind, crd.spec), report); } } - for (const pipeline of Object.values(tekton.pipelines)) { + for (const pipeline of Object.values(tekton.pipelines)) { for (const task of pipeline.spec.tasks) { checkParams(getParams('Task', task), report); } diff --git a/rules/no-duplicate-resource.js b/src/rules/no-duplicate-resource.ts similarity index 90% rename from rules/no-duplicate-resource.js rename to src/rules/no-duplicate-resource.ts index a0d70f6..9b6145b 100644 --- a/rules/no-duplicate-resource.js +++ b/src/rules/no-duplicate-resource.ts @@ -1,4 +1,4 @@ -module.exports = (docs, tekton, report) => { +export default (docs, tekton, report) => { const resourceNames = new Map(); for (const resource of docs) { diff --git a/rules/no-extra-param.js b/src/rules/no-extra-param.ts similarity index 89% rename from rules/no-extra-param.js rename to src/rules/no-extra-param.ts index 52298ff..43ad9a7 100644 --- a/rules/no-extra-param.js +++ b/src/rules/no-extra-param.ts @@ -3,8 +3,8 @@ function getTaskParams(spec) { return spec.params; } -module.exports = (docs, tekton, report) => { - for (const pipeline of Object.values(tekton.pipelines)) { +export default (docs, tekton, report) => { + for (const pipeline of Object.values(tekton.pipelines)) { for (const task of pipeline.spec.tasks) { if (task.taskRef) { const name = task.taskRef.name; @@ -24,7 +24,7 @@ module.exports = (docs, tekton, report) => { } } } - for (const pipeline of Object.values(tekton.pipelines)) { + for (const pipeline of Object.values(tekton.pipelines)) { for (const task of pipeline.spec.tasks) { if (task.taskSpec) { const params = getTaskParams(task.taskSpec); @@ -48,8 +48,8 @@ module.exports = (docs, tekton, report) => { } } } - for (const pipeline of Object.values(tekton.pipelines)) { - for (const template of Object.values(tekton.triggerTemplates)) { + for (const pipeline of Object.values(tekton.pipelines)) { + for (const template of Object.values(tekton.triggerTemplates)) { const matchingResource = template.spec.resourcetemplates.find(item => item.spec && item.spec.pipelineRef && item.spec.pipelineRef.name === pipeline.metadata.name); if (!matchingResource) continue; const pipelineParams = pipeline.spec.params || []; diff --git a/rules/no-invalid-name.js b/src/rules/no-invalid-name.ts similarity index 90% rename from rules/no-invalid-name.js rename to src/rules/no-invalid-name.ts index 276bf04..5a2f8b8 100644 --- a/rules/no-invalid-name.js +++ b/src/rules/no-invalid-name.ts @@ -1,4 +1,5 @@ -const collectResources = require('../collect-resources'); +import collectResources from '../collect-resources'; + const isValidName = (name) => { const valid = new RegExp('^[a-z0-9-()$.]*$'); return valid.test(name); @@ -10,7 +11,7 @@ function getTaskParams(spec) { } function checkInvalidParameterName(resources, report) { - for (const resource of Object.values(resources)) { + for (const resource of Object.values(resources)) { let params; if (resource.kind === 'Task') { params = getTaskParams(resource.spec); @@ -27,7 +28,7 @@ function checkInvalidParameterName(resources, report) { } } -module.exports = (docs, tekton, report) => { +export default (docs, tekton, report) => { checkInvalidParameterName(tekton.tasks, report); checkInvalidParameterName(tekton.conditions, report); checkInvalidParameterName(tekton.triggerTemplates, report); diff --git a/rules/no-latest-image.js b/src/rules/no-latest-image.ts similarity index 80% rename from rules/no-latest-image.js rename to src/rules/no-latest-image.ts index 7df1996..7c8ca58 100644 --- a/rules/no-latest-image.js +++ b/src/rules/no-latest-image.ts @@ -1,4 +1,4 @@ -module.exports = (docs, tekton, report) => { +export default (docs, tekton, report) => { function check(step) { if (!step) return; const image = step.image; @@ -8,7 +8,7 @@ module.exports = (docs, tekton, report) => { } } - for (const task of Object.values(tekton.tasks)) { + for (const task of Object.values(tekton.tasks)) { check(task.spec.stepTemplate); for (const step of Object.values(task.spec.steps)) { check(step); diff --git a/rules/no-missing-param.js b/src/rules/no-missing-param.ts similarity index 90% rename from rules/no-missing-param.js rename to src/rules/no-missing-param.ts index 43b5100..d6dadd3 100644 --- a/rules/no-missing-param.js +++ b/src/rules/no-missing-param.ts @@ -3,8 +3,8 @@ function getTaskParams(spec) { return spec.params; } -module.exports = (docs, tekton, report) => { - for (const pipeline of Object.values(tekton.pipelines)) { +export default (docs, tekton, report) => { + for (const pipeline of Object.values(tekton.pipelines)) { for (const task of pipeline.spec.tasks) { if (task.taskRef) { const name = task.taskRef.name; @@ -23,7 +23,7 @@ module.exports = (docs, tekton, report) => { } } } - for (const pipeline of Object.values(tekton.pipelines)) { + for (const pipeline of Object.values(tekton.pipelines)) { for (const task of pipeline.spec.tasks) { if (task.taskSpec) { const params = getTaskParams(task.taskSpec); @@ -52,8 +52,8 @@ module.exports = (docs, tekton, report) => { } } } - for (const pipeline of Object.values(tekton.pipelines)) { - for (const template of Object.values(tekton.triggerTemplates)) { + for (const pipeline of Object.values(tekton.pipelines)) { + for (const template of Object.values(tekton.triggerTemplates)) { const matchingResource = template.spec.resourcetemplates.find(item => item.spec && item.spec.pipelineRef && item.spec.pipelineRef.name === pipeline.metadata.name); if (!matchingResource) continue; const pipelineParams = pipeline.spec.params || []; diff --git a/rules/no-missing-resource.js b/src/rules/no-missing-resource.ts similarity index 85% rename from rules/no-missing-resource.js rename to src/rules/no-missing-resource.ts index f5ff9e0..fdedb82 100644 --- a/rules/no-missing-resource.js +++ b/src/rules/no-missing-resource.ts @@ -1,5 +1,5 @@ -module.exports = (docs, tekton, report) => { - for (const listener of Object.values(tekton.listeners)) { +export default (docs, tekton, report) => { + for (const listener of Object.values(tekton.listeners)) { for (const trigger of listener.spec.triggers) { if (!trigger.binding) continue; const name = trigger.binding.name; @@ -9,7 +9,7 @@ module.exports = (docs, tekton, report) => { } } - for (const listener of Object.values(tekton.listeners)) { + for (const listener of Object.values(tekton.listeners)) { for (const trigger of listener.spec.triggers) { if (!trigger.template) continue; const name = trigger.template.name; @@ -19,7 +19,7 @@ module.exports = (docs, tekton, report) => { } } - for (const template of Object.values(tekton.triggerTemplates)) { + for (const template of Object.values(tekton.triggerTemplates)) { for (const resourceTemplate of template.spec.resourcetemplates) { if (resourceTemplate.kind !== 'PipelineRun') continue; if (resourceTemplate.spec.pipelineSpec) continue; @@ -33,7 +33,7 @@ module.exports = (docs, tekton, report) => { } } - for (const pipeline of Object.values(tekton.pipelines)) { + for (const pipeline of Object.values(tekton.pipelines)) { for (const task of pipeline.spec.tasks) { if (!task.conditions) continue; for (const condition of task.conditions) { @@ -43,7 +43,7 @@ module.exports = (docs, tekton, report) => { } } - for (const pipeline of Object.values(tekton.pipelines)) { + for (const pipeline of Object.values(tekton.pipelines)) { for (const task of pipeline.spec.tasks) { if (!task.taskRef) continue; const name = task.taskRef.name; diff --git a/rules/no-missing-workspace.js b/src/rules/no-missing-workspace.ts similarity index 85% rename from rules/no-missing-workspace.js rename to src/rules/no-missing-workspace.ts index b2329dd..bbeea75 100644 --- a/rules/no-missing-workspace.js +++ b/src/rules/no-missing-workspace.ts @@ -1,10 +1,10 @@ -module.exports = (docs, tekton, report) => { - for (const task of Object.values(tekton.tasks)) { +export default (docs, tekton, report) => { + for (const task of Object.values(tekton.tasks)) { if (!task.spec.workspaces) continue; const taskName = task.metadata.name; const requiredWorkspaces = task.spec.workspaces.map(ws => ws.name); - for (const pipeline of Object.values(tekton.pipelines)) { + for (const pipeline of Object.values(tekton.pipelines)) { const matchingTaskRefs = pipeline.spec.tasks.filter(task => task.taskRef && task.taskRef.name === taskName); for (const taskRef of matchingTaskRefs) { @@ -19,7 +19,7 @@ module.exports = (docs, tekton, report) => { } } - for (const pipeline of Object.values(tekton.pipelines)) { + for (const pipeline of Object.values(tekton.pipelines)) { const pipelineWorkspaces = pipeline.spec.workspaces || []; for (const task of pipeline.spec.tasks) { if (!task.workspaces) continue; @@ -32,11 +32,11 @@ module.exports = (docs, tekton, report) => { } } - for (const pipeline of Object.values(tekton.pipelines)) { + for (const pipeline of Object.values(tekton.pipelines)) { if (!pipeline.spec.workspaces) continue; const required = pipeline.spec.workspaces.map(ws => ws.name); - for (const template of Object.values(tekton.triggerTemplates)) { + for (const template of Object.values(tekton.triggerTemplates)) { const pipelineRuns = template.spec.resourcetemplates.filter(item => item.spec && item.spec.pipelineRef && item.spec.pipelineRef.name === pipeline.metadata.name); for (const pipelineRun of pipelineRuns) { diff --git a/rules/no-pipeline-missing-task.js b/src/rules/no-pipeline-missing-task.ts similarity index 84% rename from rules/no-pipeline-missing-task.js rename to src/rules/no-pipeline-missing-task.ts index 5a997c5..eb7d1ed 100644 --- a/rules/no-pipeline-missing-task.js +++ b/src/rules/no-pipeline-missing-task.ts @@ -1,5 +1,5 @@ -module.exports = (docs, tekton, report) => { - for (const pipeline of Object.values(tekton.pipelines)) { +export default (docs, tekton, report) => { + for (const pipeline of Object.values(tekton.pipelines)) { for (const task of pipeline.spec.tasks) { if (!task.runAfter) continue; diff --git a/rules/no-pipeline-task-cycle.js b/src/rules/no-pipeline-task-cycle.ts similarity index 92% rename from rules/no-pipeline-task-cycle.js rename to src/rules/no-pipeline-task-cycle.ts index 3990b1e..7fe10c4 100644 --- a/rules/no-pipeline-task-cycle.js +++ b/src/rules/no-pipeline-task-cycle.ts @@ -1,4 +1,4 @@ -const { alg, Graph } = require('graphlib'); +import { alg, Graph } from 'graphlib'; const RESULT_PATTERN = '\\$\\(tasks\\.([^.]+)\\.results\\.[^.]*\\)'; const RESULT_REGEX_G = new RegExp(RESULT_PATTERN, 'g'); @@ -75,13 +75,13 @@ function errorCyclesInPipeline(pipeline, referenceCreators, report) { const pipelineTaskGraph = buildTaskGraph(pipeline, referenceCreators); for (const cycle of alg.findCycles(pipelineTaskGraph)) { for (const taskNameInCycle of cycle) { - const taskInCycle = Object.values(pipeline.spec.tasks).find(task => task.name === taskNameInCycle); + const taskInCycle = Object.values(pipeline.spec.tasks).find(task => task.name === taskNameInCycle); report(`Cycle found in tasks (dependency graph): ${[...cycle, cycle[0]].join(' -> ')}`, taskInCycle, 'name'); } } } -module.exports = (docs, tekton, report) => { +export default (docs, tekton, report) => { for (const pipeline of Object.values(tekton.pipelines)) { errorCyclesInPipeline(pipeline, [runAfterReferences, paramsReferences, resourceInputReferences], report); } diff --git a/rules/no-pipeline-task-missing-params.js b/src/rules/no-pipeline-task-missing-params.ts similarity index 56% rename from rules/no-pipeline-task-missing-params.js rename to src/rules/no-pipeline-task-missing-params.ts index 6e2c40d..14d71cf 100644 --- a/rules/no-pipeline-task-missing-params.js +++ b/src/rules/no-pipeline-task-missing-params.ts @@ -1,9 +1,9 @@ -module.exports = (docs, tekton, report) => { - for (const pipeline of Object.values(tekton.pipelines)) { +export default (docs, tekton, report) => { + for (const pipeline of Object.values(tekton.pipelines)) { if (!pipeline.spec.params) continue; - for (const task of Object.values(pipeline.spec.tasks)) { + for (const task of Object.values(pipeline.spec.tasks)) { if (!task.params) continue; - for (const param of Object.values(task.params)) { + for (const param of Object.values(task.params)) { if (typeof param.value == 'undefined') { report(`Task '${task.name}' has a parameter '${param.name}' that doesn't have a value in pipeline '${pipeline.metadata.name}'.`, param, 'name'); } diff --git a/rules/no-resourceversion.js b/src/rules/no-resourceversion.ts similarity index 83% rename from rules/no-resourceversion.js rename to src/rules/no-resourceversion.ts index 34aafc0..6116b19 100644 --- a/rules/no-resourceversion.js +++ b/src/rules/no-resourceversion.ts @@ -1,4 +1,5 @@ -const collectResources = require('../collect-resources'); +import collectResources from '../collect-resources'; + const checkInvalidResourceKey = (invalidKey, resources, report) => { Object.entries(resources).forEach(([type, resourceList]) => { Object.entries(resourceList).forEach(([name, resource]) => { @@ -9,7 +10,7 @@ const checkInvalidResourceKey = (invalidKey, resources, report) => { }); }; -module.exports = (docs, tekton, report) => { +export default (docs, tekton, report) => { const resources = collectResources(docs); checkInvalidResourceKey('resourceVersion', resources, report); }; diff --git a/rules/no-undefined-param.js b/src/rules/no-undefined-param.ts similarity index 84% rename from rules/no-undefined-param.js rename to src/rules/no-undefined-param.ts index 067539e..da72fd1 100644 --- a/rules/no-undefined-param.js +++ b/src/rules/no-undefined-param.ts @@ -1,4 +1,4 @@ -const { walk, pathToString } = require('../walk'); +import { walk, pathToString } from '../walk'; const createVisitor = (resource, params, prefix, report) => (node, path, parent) => { if (path.includes('taskSpec')) return; @@ -28,13 +28,13 @@ function getTaskParams(crd) { return []; } -module.exports = (docs, tekton, report) => { - for (const pipeline of Object.values(tekton.pipelines)) { +export default (docs, tekton, report) => { + for (const pipeline of Object.values(tekton.pipelines)) { const params = getParams(pipeline); walk(pipeline.spec.tasks, ['spec', 'tasks'], createVisitor(pipeline.metadata.name, params, 'params', report)); } - for (const task of Object.values(tekton.tasks)) { + for (const task of Object.values(tekton.tasks)) { const params = getTaskParams(task); for (const prefix of ['inputs.params', 'params']) { for (const prop of ['steps', 'volumes', 'stepTemplate', 'sidecars']) { @@ -43,7 +43,7 @@ module.exports = (docs, tekton, report) => { } } - for (const template of Object.values(tekton.triggerTemplates)) { + for (const template of Object.values(tekton.triggerTemplates)) { const params = getParams(template); walk(template.spec.resourcetemplates, ['spec', 'resourcetemplates'], createVisitor(template.metadata.name, params, 'params', report)); } diff --git a/rules/no-undefined-result.js b/src/rules/no-undefined-result.ts similarity index 88% rename from rules/no-undefined-result.js rename to src/rules/no-undefined-result.ts index 29129cb..38f7149 100644 --- a/rules/no-undefined-result.js +++ b/src/rules/no-undefined-result.ts @@ -1,4 +1,4 @@ -const { walk, pathToString } = require('../walk'); +import { walk, pathToString } from '../walk'; const taskNameRegexp = /\$\(tasks\.(.*?)\..*?\)/; @@ -19,11 +19,11 @@ const checkUndefinedResult = (pipeline, tekton, report) => (value, path, parent) } }; -module.exports = (docs, tekton, report) => { - for (const pipeline of Object.values(tekton.pipelines)) { +export default (docs, tekton, report) => { + for (const pipeline of Object.values(tekton.pipelines)) { walk(pipeline, [], checkUndefinedResult(pipeline, tekton, report)); } - for (const pipeline of Object.values(tekton.pipelines)) { + for (const pipeline of Object.values(tekton.pipelines)) { for (const task of pipeline.spec.tasks) { if (!task.params) continue; for (const param of task.params) { diff --git a/rules/no-undefined-volume.js b/src/rules/no-undefined-volume.ts similarity index 82% rename from rules/no-undefined-volume.js rename to src/rules/no-undefined-volume.ts index b4cec73..08dbc38 100644 --- a/rules/no-undefined-volume.js +++ b/src/rules/no-undefined-volume.ts @@ -1,5 +1,5 @@ -module.exports = (docs, tekton, report) => { - for (const task of Object.values(tekton.tasks)) { +export default (docs, tekton, report) => { + for (const task of Object.values(tekton.tasks)) { const volumes = (task.spec.volumes || []).map(volume => volume.name); for (const step of task.spec.steps) { diff --git a/rules/no-unused-param.js b/src/rules/no-unused-param.ts similarity index 88% rename from rules/no-unused-param.js rename to src/rules/no-unused-param.ts index 1cb1c2a..a50c956 100644 --- a/rules/no-unused-param.js +++ b/src/rules/no-unused-param.ts @@ -1,4 +1,4 @@ -const { walk } = require('../walk'); +import { walk } from '../walk'; const unused = (params, prefix) => (node) => { const r1 = new RegExp(`\\$\\(${prefix}.(.*?)\\)`, 'g'); @@ -22,8 +22,8 @@ function getParams(kind, spec) { return []; } -module.exports = (docs, tekton, report) => { - for (const task of Object.values(tekton.tasks)) { +export default (docs, tekton, report) => { + for (const task of Object.values(tekton.tasks)) { const params = getParams(task.kind, task.spec); const occurences = Object.fromEntries(params.map(param => [param.name, 0])); for (const prefix of ['inputs.params', 'params']) { @@ -37,7 +37,7 @@ module.exports = (docs, tekton, report) => { } } - for (const condition of Object.values(tekton.conditions)) { + for (const condition of Object.values(tekton.conditions)) { const params = getParams(condition.kind, condition.spec); const occurences = Object.fromEntries(params.map(param => [param.name, 0])); walk(condition.spec.check, ['spec', 'check'], unused(occurences, 'params')); @@ -47,7 +47,7 @@ module.exports = (docs, tekton, report) => { } } - for (const template of Object.values(tekton.triggerTemplates)) { + for (const template of Object.values(tekton.triggerTemplates)) { const params = getParams(template.kind, template.spec); const occurences = Object.fromEntries(params.map(param => [param.name, 0])); walk(template.spec, ['spec'], unused(occurences, 'params')); @@ -57,7 +57,7 @@ module.exports = (docs, tekton, report) => { } } - for (const pipeline of Object.values(tekton.pipelines)) { + for (const pipeline of Object.values(tekton.pipelines)) { const params = getParams(pipeline.kind, pipeline.spec); const occurences = Object.fromEntries(params.map(param => [param.name, 0])); walk(pipeline.spec.tasks, ['spec', 'tasks'], unused(occurences, 'params')); diff --git a/rules/no-wrong-param-type.js b/src/rules/no-wrong-param-type.ts similarity index 81% rename from rules/no-wrong-param-type.js rename to src/rules/no-wrong-param-type.ts index a117d6a..78080d0 100644 --- a/rules/no-wrong-param-type.js +++ b/src/rules/no-wrong-param-type.ts @@ -14,19 +14,19 @@ function getTaskParams(spec) { return spec.params; } -module.exports = (docs, tekton, report) => { - for (const task of Object.values(tekton.tasks)) { +export default (docs, tekton, report) => { + for (const task of Object.values(tekton.tasks)) { const params = getTaskParams(task.spec); if (!params) continue; checkParameterValues(task.metadata.name, task.kind, params, report); } - for (const template of Object.values(tekton.triggerTemplates)) { + for (const template of Object.values(tekton.triggerTemplates)) { if (!template.spec.params) continue; checkParameterValues(template.metadata.name, template.kind, template.spec.params, report); } - for (const pipeline of Object.values(tekton.pipelines)) { + for (const pipeline of Object.values(tekton.pipelines)) { if (!pipeline.spec.params) continue; checkParameterValues(pipeline.metadata.name, pipeline.kind, pipeline.spec.params, report); } diff --git a/rules/prefer-beta.js b/src/rules/prefer-beta.ts similarity index 86% rename from rules/prefer-beta.js rename to src/rules/prefer-beta.ts index 69c889e..cff39d7 100644 --- a/rules/prefer-beta.js +++ b/src/rules/prefer-beta.ts @@ -1,5 +1,5 @@ -module.exports = (docs, tekton, report) => { - for (const pipeline of Object.values(tekton.pipelines)) { +export default (docs, tekton, report) => { + for (const pipeline of Object.values(tekton.pipelines)) { for (const task of pipeline.spec.tasks) { if (!task.taskSpec) continue; switch (pipeline.apiVersion) { @@ -13,7 +13,7 @@ module.exports = (docs, tekton, report) => { } } - for (const task of Object.values(tekton.tasks)) { + for (const task of Object.values(tekton.tasks)) { switch (task.apiVersion) { case 'tekton.dev/v1alpha1': if (task.spec.params) report(`Task '${task.metadata.name}' is defined with apiVersion tekton.dev/v1alpha1, but defines spec.params. Use spec.inputs.params instead.`, task.spec.params); @@ -24,13 +24,13 @@ module.exports = (docs, tekton, report) => { } } - for (const task of Object.values(tekton.tasks)) { + for (const task of Object.values(tekton.tasks)) { if (task.apiVersion === 'tekton.dev/v1alpha1') { report(`Task '${task.metadata.name}' is defined with apiVersion tekton.dev/v1alpha1, consider migrating to tekton.dev/v1beta1`, task, 'apiVersion'); } } - for (const pipeline of Object.values(tekton.pipelines)) { + for (const pipeline of Object.values(tekton.pipelines)) { if (pipeline.apiVersion === 'tekton.dev/v1alpha1') { report(`Pipeline '${pipeline.metadata.name}' is defined with apiVersion tekton.dev/v1alpha1, consider migrating to tekton.dev/v1beta1`, pipeline, 'apiVersion'); } diff --git a/rules/prefer-kebab-case.js b/src/rules/prefer-kebab-case.ts similarity index 86% rename from rules/prefer-kebab-case.js rename to src/rules/prefer-kebab-case.ts index 325bd56..2e4af2f 100644 --- a/rules/prefer-kebab-case.js +++ b/src/rules/prefer-kebab-case.ts @@ -1,4 +1,4 @@ -const { walk, pathToString } = require('../walk'); +import { walk, pathToString } from '../walk'; const isValidName = (name) => { const valid = new RegExp('^[a-z0-9-()$.]*$'); @@ -27,8 +27,8 @@ const naming = (resource, prefix, report) => (node, path, parent) => { } }; -module.exports = (docs, tekton, report) => { - for (const pipeline of Object.values(tekton.pipelines)) { +export default (docs, tekton, report) => { + for (const pipeline of Object.values(tekton.pipelines)) { walk(pipeline.spec.tasks, ['spec', 'tasks'], naming(pipeline.metadata.name, 'params', report)); } }; diff --git a/rules/prefer-when-expression.js b/src/rules/prefer-when-expression.ts similarity index 78% rename from rules/prefer-when-expression.js rename to src/rules/prefer-when-expression.ts index dcbb664..68a0eea 100644 --- a/rules/prefer-when-expression.js +++ b/src/rules/prefer-when-expression.ts @@ -1,5 +1,5 @@ -module.exports = (docs, tekton, report) => { - for (const pipeline of Object.values(tekton.pipelines)) { +export default (docs, tekton, report) => { + for (const pipeline of Object.values(tekton.pipelines)) { for (const task of pipeline.spec.tasks) { if (task.conditions) { const guardedBy = task.conditions.map(condition => condition.conditionRef); diff --git a/runner.js b/src/runner.ts similarity index 57% rename from runner.js rename to src/runner.ts index 48eb38d..f545842 100644 --- a/runner.js +++ b/src/runner.ts @@ -1,12 +1,12 @@ -const fs = require('fs'); -const path = require('path'); -const yaml = require('yaml'); -const collector = require('./Collector'); -const Reporter = require('./reporter'); -const rules = require('./rules'); +import fs from 'fs'; +import path from 'path'; +import yaml from 'yaml'; +import collector from './Collector'; +import Reporter from './reporter'; +import { lint as doLint } from './rules'; const getRulesConfig = () => { - const defaultRcFile = fs.readFileSync(path.resolve(__dirname, '.tektonlintrc.yaml'), 'utf8'); + const defaultRcFile = fs.readFileSync(path.resolve(__dirname, '..', '.tektonlintrc.yaml'), 'utf8'); const defaultConfig = yaml.parse(defaultRcFile); if (fs.existsSync('./.tektonlintrc.yaml')) { @@ -20,14 +20,14 @@ const getRulesConfig = () => { return defaultConfig; }; -module.exports = async function runner(globs) { - const docs = await collector(globs); - const reporter = new Reporter(docs); - return module.exports.lint(docs.map(doc => doc.content), reporter); -}; - -module.exports.lint = function lint(docs, reporter) { +export function lint(docs, reporter) { reporter = reporter || new Reporter(); const config = getRulesConfig(); - return rules.lint(docs, reporter, config); + return doLint(docs, reporter, config); +}; + +export default async function runner(globs) { + const docs = await collector(globs); + const reporter = new Reporter(docs); + return lint(docs.map(doc => doc.content), reporter); }; diff --git a/utils.js b/src/utils.ts similarity index 74% rename from utils.js rename to src/utils.ts index a535223..a579888 100644 --- a/utils.js +++ b/src/utils.ts @@ -1,6 +1,6 @@ -const chalk = require('chalk'); +import chalk from 'chalk'; -const formatLine = (problem) => { +export function formatLine(problem) { const level = problem.level === 'error' ? `${chalk.red(problem.level)} ` : chalk.yellow(problem.level); if (problem.loc) { return `${level} (${problem.loc.startLine},${problem.loc.startColumn},${problem.loc.endLine},${problem.loc.endColumn}): ${problem.message}`; @@ -8,13 +8,13 @@ const formatLine = (problem) => { return `${level}: ${problem.message}`; }; -const logProblems = (problems) => { +export function logProblems(problems) { const groupByFile = problems.reduce((group, problem) => { group[problem.path] = (group[problem.path] || []).concat(problem); return group; }, {}); - for (const [file, fileProblems] of Object.entries(groupByFile)) { + for (const [file, fileProblems] of Object.entries(groupByFile)) { console.log(`${chalk.bold(file)}:`); for (const problem of fileProblems) { console.log(formatLine(problem)); @@ -22,8 +22,3 @@ const logProblems = (problems) => { console.log('\n'); } }; - -module.exports = { - logProblems, - formatLine, -}; diff --git a/walk.js b/src/walk.ts similarity index 80% rename from walk.js rename to src/walk.ts index 57c8f2e..fe044c9 100644 --- a/walk.js +++ b/src/walk.ts @@ -1,4 +1,4 @@ -function walk(node, path, visitor, parent) { +export function walk(node, path, visitor, parent?) { if (typeof node === 'string' || typeof node === 'number') { visitor(node, path, parent); } else if (Array.isArray(node)) { @@ -13,7 +13,7 @@ function walk(node, path, visitor, parent) { } } -function pathToString(path) { +export function pathToString(path) { let str = ''; for (const segment of path) { if (typeof segment == 'number') { @@ -27,6 +27,3 @@ function pathToString(path) { } return str; } - -module.exports.walk = walk; -module.exports.pathToString = pathToString; diff --git a/watch.js b/src/watch.ts similarity index 87% rename from watch.js rename to src/watch.ts index a63f4a0..15c5bd9 100644 --- a/watch.js +++ b/src/watch.ts @@ -1,6 +1,6 @@ -const chokidar = require('chokidar'); -const run = require('./runner'); -const { logProblems } = require('./utils'); +import chokidar from 'chokidar'; +import run from './runner'; +import { logProblems } from './utils'; const runLinter = async (cause, paths) => { console.log(cause); @@ -9,7 +9,7 @@ const runLinter = async (cause, paths) => { console.log('Tekton-lint finished running!'); }; -module.exports = (paths) => { +export default (paths) => { const watcher = chokidar.watch(paths, { persistent: true, }); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..e605cd4 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "es2020", + "module": "commonjs", + "lib": ["ES2020"], + "outDir": "./lib", + "strict": false, + "moduleResolution": "node", + "esModuleInterop": true, + "inlineSourceMap": true, + "inlineSources": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "declaration": true + }, + "include": [ + "src/**/*.ts" + ] +}