From 5a423bcdad661563a983f9b554f5461a07cd7905 Mon Sep 17 00:00:00 2001 From: MBWhite Date: Mon, 15 Apr 2024 11:43:13 +0100 Subject: [PATCH] feat: add support for camelCase Signed-off-by: MBWhite --- .tektonlintrc.yaml | 1 + .../customconfig/.tektonlintrc.yaml | 8 +++ .../customconfig/ace-pipeline.yaml | 72 +++++++++++++++++++ .../ace-pipeline.yaml.expect.json | 53 ++++++++++++++ regression-tests/regression.test.ts | 26 ++++++- src/config.ts | 9 ++- src/default-rule-config.ts | 1 + src/rule-loader.ts | 3 + src/rules/prefer-camel-kebab-case.ts | 65 +++++++++++++++++ 9 files changed, 235 insertions(+), 3 deletions(-) create mode 100644 regression-tests/customconfig/.tektonlintrc.yaml create mode 100644 regression-tests/customconfig/ace-pipeline.yaml create mode 100644 regression-tests/customconfig/ace-pipeline.yaml.expect.json create mode 100644 src/rules/prefer-camel-kebab-case.ts diff --git a/.tektonlintrc.yaml b/.tektonlintrc.yaml index 087a332..d2c67f3 100644 --- a/.tektonlintrc.yaml +++ b/.tektonlintrc.yaml @@ -17,6 +17,7 @@ rules: # error | warning | off no-latest-image: warning prefer-beta: warning prefer-kebab-case: warning + prefer-camel-kebab-case: off no-unused-param: warning no-missing-resource: error no-undefined-param: error diff --git a/regression-tests/customconfig/.tektonlintrc.yaml b/regression-tests/customconfig/.tektonlintrc.yaml new file mode 100644 index 0000000..ce729da --- /dev/null +++ b/regression-tests/customconfig/.tektonlintrc.yaml @@ -0,0 +1,8 @@ +--- +rules: # error | warning | off + prefer-kebab-case: off + prefer-camel-kebab-case: warning + +# custom: +# my_rules: custom_rules + diff --git a/regression-tests/customconfig/ace-pipeline.yaml b/regression-tests/customconfig/ace-pipeline.yaml new file mode 100644 index 0000000..876b607 --- /dev/null +++ b/regression-tests/customconfig/ace-pipeline.yaml @@ -0,0 +1,72 @@ +apiVersion: tekton.dev/v1beta1 +kind: Pipeline +metadata: + name: ace-pipeline +spec: + params: + - name: outputRegistry + type: string + - name: url + type: string + default: "https://github.com/ot4i/ace-demo-pipeline" + - name: revision + type: string + default: "main" + - name: buildImage + type: string + - name: runtimeBaseImage + type: string + - name: knativeDeploy + type: string + default: "false" + tasks: + - name: build-from-source + taskRef: + name: aceBuild + params: + - name: outputRegistry + value: $(params.outputRegistry) + - name: url + value: $(params.url) + - name: revision + value: $(params.revision) + - name: buildImage + value: $(params.buildImage) + - name: runtimeBaseImage + value: $(params.runtimeBaseImage) + - name: deploy-to-cluster + taskRef: + name: deploy-to-cluster + params: + - name: dockerRegistry + value: $(params.outputRegistry) + - name: url + value: $(params.url) + - name: revision + value: $(params.revision) + - name: tag + value: "$(tasks.build-from-source.results.tag)" + runAfter: + - build-from-source + when: + - input: "$(params.knativeDeploy)" + operator: in + values: ["false"] + - name: deploy-knative-to-cluster + taskRef: + name: knative-deploy + params: + - name: dockerRegistry + value: $(params.outputRegistry) + - name: url + value: $(params.url) + - name: revision + value: $(params.revision) + - name: tag + value: "$(tasks.build-from-source.results.tag)" + runAfter: + - build-from-source + when: + - input: "$(params.knativeDeploy)" + operator: in + values: ["true"] \ No newline at end of file diff --git a/regression-tests/customconfig/ace-pipeline.yaml.expect.json b/regression-tests/customconfig/ace-pipeline.yaml.expect.json new file mode 100644 index 0000000..e69160d --- /dev/null +++ b/regression-tests/customconfig/ace-pipeline.yaml.expect.json @@ -0,0 +1,53 @@ +[ + { + "message": "Pipeline 'ace-pipeline' references task 'aceBuild' but the referenced task cannot be found. To fix this, include all the task definitions to the lint task for this pipeline.", + "rule": "no-missing-resource", + "level": "error", + "path": "./regression-tests/customconfig/ace-pipeline.yaml", + "loc": { + "range": [ + 521, + 529, + 530 + ], + "startLine": 25, + "startColumn": 15, + "endLine": 25, + "endColumn": 23 + } + }, + { + "message": "Pipeline 'ace-pipeline' references task 'deploy-to-cluster' but the referenced task cannot be found. To fix this, include all the task definitions to the lint task for this pipeline.", + "rule": "no-missing-resource", + "level": "error", + "path": "./regression-tests/customconfig/ace-pipeline.yaml", + "loc": { + "range": [ + 930, + 947, + 948 + ], + "startLine": 39, + "startColumn": 15, + "endLine": 39, + "endColumn": 32 + } + }, + { + "message": "Pipeline 'ace-pipeline' references task 'knative-deploy' but the referenced task cannot be found. To fix this, include all the task definitions to the lint task for this pipeline.", + "rule": "no-missing-resource", + "level": "error", + "path": "./regression-tests/customconfig/ace-pipeline.yaml", + "loc": { + "range": [ + 1442, + 1456, + 1457 + ], + "startLine": 57, + "startColumn": 15, + "endLine": 57, + "endColumn": 29 + } + } +] \ No newline at end of file diff --git a/regression-tests/regression.test.ts b/regression-tests/regression.test.ts index 925f828..31be7fe 100644 --- a/regression-tests/regression.test.ts +++ b/regression-tests/regression.test.ts @@ -1,12 +1,13 @@ import fs from 'node:fs' import fg from 'fast-glob'; +import path from 'node:path'; import {Problem, Config, Linter} from '../src/index' const pattern = "./regression-tests/general/*.yaml" const yamlfiles = fg.globSync(pattern) +const customconfig = "./regression-tests/customconfig/*.yaml" - -describe("Regression Tests",()=>{ +describe("Default Config Regression Tests",()=>{ test.each(yamlfiles)("%s",async (yamlSrcPath)=>{ const cfg: Config = Config.getDefaultConfig() @@ -23,4 +24,25 @@ describe("Regression Tests",()=>{ expect(problems).toMatchObject(expected) }) +}) + +describe("Custom Config Regression Tests",()=>{ + + test.each(fg.globSync(customconfig))("%s", async (yamlSrcPath)=>{ + const cfgPath = path.resolve(path.dirname(yamlSrcPath)) + const cfg: Config = Config.getConfig(cfgPath); + cfg.globs=[yamlSrcPath] + const problems: Problem[] = await Linter.run(cfg) + + const expectedPath =`${yamlSrcPath}.expect.json` + + if (!fs.existsSync(expectedPath)){ + fs.writeFileSync(expectedPath, JSON.stringify(problems)) + } + + const expected = JSON.parse(fs.readFileSync(expectedPath,'utf-8')) + expect(problems).toMatchObject(expected) + + }) + }) \ No newline at end of file diff --git a/src/config.ts b/src/config.ts index c2d9bdf..013dec5 100644 --- a/src/config.ts +++ b/src/config.ts @@ -103,18 +103,25 @@ export class Config { public static getDefaultConfig(): Config { // create default cli-proxy + return Config.getConfig(process.cwd()); + } + + public static getConfig(dir: string): Config { + // create default cli-proxy const argv = { watch: false, color: true, format: json, quite: false, - config: process.cwd(), + config: dir, 'refresh-cache': false, + _: [], }; const config = new Config(argv); return config; } + } diff --git a/src/default-rule-config.ts b/src/default-rule-config.ts index d51ec5c..9186f8e 100644 --- a/src/default-rule-config.ts +++ b/src/default-rule-config.ts @@ -19,6 +19,7 @@ const defaultRules: RulesConfig = { 'no-latest-image': 'warning', 'prefer-beta': 'warning', 'prefer-kebab-case': 'warning', + 'prefer-camel-kebab-case': 'off', 'no-unused-param': 'warning', 'no-missing-resource': 'error', 'no-undefined-param': 'error', diff --git a/src/rule-loader.ts b/src/rule-loader.ts index add736c..4688e32 100644 --- a/src/rule-loader.ts +++ b/src/rule-loader.ts @@ -55,6 +55,9 @@ const defaultRules = { // prefer-kebab-case 'prefer-kebab-case': (await import('./rules/prefer-kebab-case.js')).default, + // prefer-camel-kebab-case + 'prefer-camel-kebab-case': (await import('./rules/prefer-camel-kebab-case.js')).default, + // prefer-when-expression 'prefer-when-expression': (await import('./rules/prefer-when-expression.js')).default, diff --git a/src/rules/prefer-camel-kebab-case.ts b/src/rules/prefer-camel-kebab-case.ts new file mode 100644 index 0000000..474748e --- /dev/null +++ b/src/rules/prefer-camel-kebab-case.ts @@ -0,0 +1,65 @@ +import { walk, pathToString } from '../walk.js'; + +const isValidKebabName = (name) => { + const valid = new RegExp('^[a-z0-9-()$.]*$'); + return valid.test(name); +}; + +const isValidCamelName = (name) => { + const valid = new RegExp('^[a-z_][a-z0-9A-Z()$.]*$'); + return valid.test(name); +}; + +const isValidName = (name) => { + return isValidKebabName(name) || isValidCamelName(name); +}; + +const naming = (resource, prefix, report) => (node, path, parent) => { + let name = node; + const isNameDefinition = /.name$/.test(path); + + if (path.includes('env') && path.includes('name')) return; + + if (isNameDefinition && !isValidName(name)) { + report( + `Invalid name for '${name}' at ${pathToString( + path, + )} in '${resource}'. Names should be in lowercase, alphanumeric, kebab-case or camelCase format.`, + parent, + 'name', + ); + return; + } + + const parameterPlacementRx = new RegExp(`\\$\\(${prefix}.(.*?)\\)`); + const m = node && node.toString().match(parameterPlacementRx); + + if (m) { + name = m[1]; + if (!isValidName(name)) { + report( + `Invalid name for '${name}' at ${pathToString( + path, + )} in '${resource}'. Names should be in lowercase, alphanumeric, kebab-case or camelCase format.`, + parent, + path[path.length - 1], + ); + } + } +}; + +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)); + walk(pipeline.spec.finally, ['spec', 'finally'], naming(pipeline.metadata.name, 'params', report)); + } + + for (const pipeline of Object.values(tekton.pipelineRuns)) { + walk(pipeline.spec.tasks, ['spec', 'pipelineSpec', 'tasks'], naming(pipeline.metadata.name, 'params', report)); + walk( + pipeline.spec.finally, + ['spec', 'pipelineSpec', 'finally'], + naming(pipeline.metadata.name, 'params', report), + ); + } +};