diff --git a/.automation/test/shell/README.md b/.automation/test/bash/README.md similarity index 100% rename from .automation/test/shell/README.md rename to .automation/test/bash/README.md diff --git a/.automation/test/shell/reports/expected-BASH.tap b/.automation/test/bash/reports/expected-BASH.tap similarity index 100% rename from .automation/test/shell/reports/expected-BASH.tap rename to .automation/test/bash/reports/expected-BASH.tap diff --git a/.automation/test/shell/shell_bad_1.sh b/.automation/test/bash/shell_bad_1.sh similarity index 100% rename from .automation/test/shell/shell_bad_1.sh rename to .automation/test/bash/shell_bad_1.sh diff --git a/.automation/test/shell/shell_good_1.sh b/.automation/test/bash/shell_good_1.sh similarity index 100% rename from .automation/test/shell/shell_good_1.sh rename to .automation/test/bash/shell_good_1.sh diff --git a/.automation/test/bash_exec/README.md b/.automation/test/bash_exec/README.md new file mode 100644 index 00000000..9b1c0d38 --- /dev/null +++ b/.automation/test/bash_exec/README.md @@ -0,0 +1,19 @@ +# Bash Test Cases + +This folder holds the test cases for **Bash Exec**. + +## Additional Docs + +No Additional information is needed for this test case. + +## Good Test Cases + +The test cases denoted: `LANGUAGE_good_FILE.EXTENSION` are all valid, and should pass successfully when linted. + +- **Note:** They are linted utilizing the default linter rules. + +## Bad Test Cases + +The test cases denoted: `LANGUAGE_bad_FILE.EXTENSION` are **NOT** valid, and should trigger errors when linted. + +- **Note:** They are linted utilizing the default linter rules. diff --git a/.automation/test/bash_exec/shell_bad_1.sh b/.automation/test/bash_exec/shell_bad_1.sh new file mode 100644 index 00000000..aa2a95e7 --- /dev/null +++ b/.automation/test/bash_exec/shell_bad_1.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +# CMD +HELLO_WORLD=($(echo "Hello World" | cut -f1 -d' ' 2>&1)) + +# Load the error code +ERROR_CODE=$? + +# Check the shell +if [ $ERROR_CODE -ne 0]; then + echo "We did it!" + exit 0 +else + echo "We done goofed it..." + echo $HELLO_WORLD + exit 1 +fi diff --git a/.automation/test/bash_exec/shell_good_1.sh b/.automation/test/bash_exec/shell_good_1.sh new file mode 100755 index 00000000..ee5435e5 --- /dev/null +++ b/.automation/test/bash_exec/shell_good_1.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +# CMD +HELLO_WORLD=$(echo "Hello World" | cut -f1 -d' ' 2>&1) + +# Load the error code +ERROR_CODE=$? + +# Check the shell +if [ ${ERROR_CODE} -ne 0 ]; then + echo "We did it!" + exit 0 +else + echo "We done goofed it..." + echo "${HELLO_WORLD}" + exit 1 +fi diff --git a/.automation/test/docker/README.md b/.automation/test/dockerfile/README.md similarity index 100% rename from .automation/test/docker/README.md rename to .automation/test/dockerfile/README.md diff --git a/.automation/test/docker/bad/Dockerfile b/.automation/test/dockerfile/bad/Dockerfile similarity index 100% rename from .automation/test/docker/bad/Dockerfile rename to .automation/test/dockerfile/bad/Dockerfile diff --git a/.automation/test/docker/good/Dockerfile b/.automation/test/dockerfile/good/Dockerfile similarity index 100% rename from .automation/test/docker/good/Dockerfile rename to .automation/test/dockerfile/good/Dockerfile diff --git a/.automation/test/docker/good/Dockerfile.dev b/.automation/test/dockerfile/good/Dockerfile.dev similarity index 100% rename from .automation/test/docker/good/Dockerfile.dev rename to .automation/test/dockerfile/good/Dockerfile.dev diff --git a/.automation/test/docker/reports/expected-DOCKER.tap b/.automation/test/dockerfile/reports/expected-DOCKER.tap similarity index 100% rename from .automation/test/docker/reports/expected-DOCKER.tap rename to .automation/test/dockerfile/reports/expected-DOCKER.tap diff --git a/.automation/test/dockerfile_hadolint/README.md b/.automation/test/dockerfile_hadolint/README.md new file mode 100644 index 00000000..cdc0a8a4 --- /dev/null +++ b/.automation/test/dockerfile_hadolint/README.md @@ -0,0 +1,18 @@ +# Docker Test Cases + +This folder holds the test cases for **Docker**. + +## Additional Docs + +Due to the nature of the naming of files, we have `2` subfolders in this directory. + +- `good` is for working, and correct **Dockerfile**(s) +- `bad` is for invalid, and incorrect **Dockerfile**(s) + +## Good Test Cases + +- **Note:** They are linted utilizing the default linter rules. + +## Bad Test Cases + +- **Note:** They are linted utilizing the default linter rules. diff --git a/.automation/test/dockerfile_hadolint/bad/Dockerfile b/.automation/test/dockerfile_hadolint/bad/Dockerfile new file mode 100644 index 00000000..4c9c4c72 --- /dev/null +++ b/.automation/test/dockerfile_hadolint/bad/Dockerfile @@ -0,0 +1,14 @@ +from node:latest + +# Create app directory +run mkdir -p /usr/src/app +WORKDIR /usr/src/app + +# Install app dependencies +copy package.json /usr/src/app/ /here/there +RUN sudo npm install + +ADD server.js server.js +EXPOSE 1 +CMD ["node", "server.js"] +ENtrypoint /tmp/here.sh diff --git a/.automation/test/dockerfile_hadolint/good/Dockerfile b/.automation/test/dockerfile_hadolint/good/Dockerfile new file mode 100644 index 00000000..33ed48d0 --- /dev/null +++ b/.automation/test/dockerfile_hadolint/good/Dockerfile @@ -0,0 +1,13 @@ +FROM node:10 + +# Create app directory +RUN mkdir -p /usr/src/app +WORKDIR /usr/src/app + +# Install app dependencies +COPY package.json /usr/src/app/ +RUN npm install + +COPY server.js server.js +EXPOSE 3000 +CMD ["node", "server.js"] diff --git a/.automation/test/dockerfile_hadolint/good/Dockerfile.dev b/.automation/test/dockerfile_hadolint/good/Dockerfile.dev new file mode 100644 index 00000000..33ed48d0 --- /dev/null +++ b/.automation/test/dockerfile_hadolint/good/Dockerfile.dev @@ -0,0 +1,13 @@ +FROM node:10 + +# Create app directory +RUN mkdir -p /usr/src/app +WORKDIR /usr/src/app + +# Install app dependencies +COPY package.json /usr/src/app/ +RUN npm install + +COPY server.js server.js +EXPOSE 3000 +CMD ["node", "server.js"] diff --git a/.automation/test/dockerfile_hadolint/reports/expected-DOCKER.tap b/.automation/test/dockerfile_hadolint/reports/expected-DOCKER.tap new file mode 100644 index 00000000..6aafb4c6 --- /dev/null +++ b/.automation/test/dockerfile_hadolint/reports/expected-DOCKER.tap @@ -0,0 +1,7 @@ +TAP version 13 +1..2 +not ok 1 - Dockerfile + --- + message: \nFile /tmp/lint/.automation/test/docker/bad/Dockerfile\nIssues 6\n\nLine 1 from node latest\nIssue Category Title Description\n 1 Clarity Capitalize For clarity and readability, all instructions in a Dockerfile\n Dockerfile should be uppercase.\n Instructions This is a convention adopted by most of the official images and\n greatly improves readability in long Dockerfiles. For an example\n of\n why this makes a difference, check out the current [redis\n Dockerfile](https //github.com/docker-library/redis/blob/b375650fb6\n 9b7db819e90c0033433c705b28656e/3.0/Dockerfile)\n and you should be able to easily see the instructions used.\n 2 Clarity Base Image Latest Base images should not use the latest tag.\n Tag\n\nLine 4 run mkdir -p /usr/src/app\nIssue Category Title Description\n 3 Clarity Capitalize For clarity and readability, all instructions in a Dockerfile\n Dockerfile should be uppercase.\n Instructions This is a convention adopted by most of the official images and\n greatly improves readability in long Dockerfiles. For an example\n of\n why this makes a difference, check out the current [redis\n Dockerfile](https //github.com/docker-library/redis/blob/b375650fb6\n 9b7db819e90c0033433c705b28656e/3.0/Dockerfile)\n and you should be able to easily see the instructions used.\n\nLine 8 copy package.json /usr/src/app/ /here/there\nIssue Category Title Description\n 4 Clarity Capitalize For clarity and readability, all instructions in a Dockerfile\n Dockerfile should be uppercase.\n Instructions This is a convention adopted by most of the official images and\n greatly improves readability in long Dockerfiles. For an example\n of\n why this makes a difference, check out the current [redis\n Dockerfile](https //github.com/docker-library/redis/blob/b375650fb6\n 9b7db819e90c0033433c705b28656e/3.0/Dockerfile)\n and you should be able to easily see the instructions used.\n\nLine 9 RUN sudo npm install\nIssue Category Title Description\n 5 Possible Bug Use Of sudo Is Not Use of `sudo` is not allowed in a Dockerfile. From the official\n Allowed document [Best practices for writing\n Dockerfiles](https //docs.docker.com/engine/userguide/eng-image/doc\n kerfile_best-practices/) \n > You should avoid installing or using `sudo` since it has\n unpredictable TTY and signal-forwarding behavior that can cause\n more problems than it solves.\n > If you absolutely need functionality similar to `sudo` (e.g.,\n initializing the daemon as root but running it as non-root), you\n may be able to use `gosu`.\n\nLine 14 ENtrypoint /tmp/here.sh\nIssue Category Title Description\n 6 Clarity Capitalize For clarity and readability, all instructions in a Dockerfile\n Dockerfile should be uppercase.\n Instructions This is a convention adopted by most of the official images and\n greatly improves readability in long Dockerfiles. For an example\n of\n why this makes a difference, check out the current [redis\n Dockerfile](https //github.com/docker-library/redis/blob/b375650fb6\n 9b7db819e90c0033433c705b28656e/3.0/Dockerfile)\n and you should be able to easily see the instructions used.\n + ... +ok 2 - Dockerfile diff --git a/.automation/test/golang/README.md b/.automation/test/go/README.md similarity index 100% rename from .automation/test/golang/README.md rename to .automation/test/go/README.md diff --git a/.automation/test/golang/golang_bad_01.go b/.automation/test/go/golang_bad_01.go similarity index 100% rename from .automation/test/golang/golang_bad_01.go rename to .automation/test/go/golang_bad_01.go diff --git a/.automation/test/golang/golang_good_01.go b/.automation/test/go/golang_good_01.go similarity index 100% rename from .automation/test/golang/golang_good_01.go rename to .automation/test/go/golang_good_01.go diff --git a/.automation/test/golang/reports/expected-GO.tap.ignored b/.automation/test/go/reports/expected-GO.tap.ignored similarity index 100% rename from .automation/test/golang/reports/expected-GO.tap.ignored rename to .automation/test/go/reports/expected-GO.tap.ignored diff --git a/.automation/test/javascript/README.md b/.automation/test/javascript_es/README.md similarity index 100% rename from .automation/test/javascript/README.md rename to .automation/test/javascript_es/README.md diff --git a/.automation/test/javascript/javascript_bad_1.js b/.automation/test/javascript_es/javascript_bad_1.js similarity index 100% rename from .automation/test/javascript/javascript_bad_1.js rename to .automation/test/javascript_es/javascript_bad_1.js diff --git a/.automation/test/javascript/javascript_good_1.js b/.automation/test/javascript_es/javascript_good_1.js similarity index 100% rename from .automation/test/javascript/javascript_good_1.js rename to .automation/test/javascript_es/javascript_good_1.js diff --git a/.automation/test/javascript/reports/expected-JAVASCRIPT_ES.tap b/.automation/test/javascript_es/reports/expected-JAVASCRIPT_ES.tap similarity index 100% rename from .automation/test/javascript/reports/expected-JAVASCRIPT_ES.tap rename to .automation/test/javascript_es/reports/expected-JAVASCRIPT_ES.tap diff --git a/.automation/test/javascript/reports/expected-JAVASCRIPT_STANDARD.tap b/.automation/test/javascript_es/reports/expected-JAVASCRIPT_STANDARD.tap similarity index 100% rename from .automation/test/javascript/reports/expected-JAVASCRIPT_STANDARD.tap rename to .automation/test/javascript_es/reports/expected-JAVASCRIPT_STANDARD.tap diff --git a/.automation/test/javascript_standard/README.md b/.automation/test/javascript_standard/README.md new file mode 100644 index 00000000..6917a7e1 --- /dev/null +++ b/.automation/test/javascript_standard/README.md @@ -0,0 +1,19 @@ +# Javascript Test Cases + +This folder holds the test cases for **Javascript**. + +## Additional Docs + +No Additional information is needed for this test case. + +## Good Test Cases + +The test cases denoted: `LANGUAGE_good_FILE.EXTENSION` are all valid, and should pass successfully when linted. + +- **Note:** They are linted utilizing the default linter rules. + +## Bad Test Cases + +The test cases denoted: `LANGUAGE_bad_FILE.EXTENSION` are **NOT** valid, and should trigger errors when linted. + +- **Note:** They are linted utilizing the default linter rules. diff --git a/.automation/test/javascript_standard/javascript_bad_1.js b/.automation/test/javascript_standard/javascript_bad_1.js new file mode 100644 index 00000000..98e5ee29 --- /dev/null +++ b/.automation/test/javascript_standard/javascript_bad_1.js @@ -0,0 +1,225 @@ +var http = require('http') +var createHandler = require( 'github-webhook-handler') + +var handler = createHandler( { path : /webhook, secret : (process.env.SECRET) }) + +var userArray = [ 'user1' ] +here is some garbage = that + +var teamDescription = Team of Robots +var teamPrivacy = 'closed' // closed (visible) / secret (hidden) are options here + +var teamName = process.env.GHES_TEAM_NAME +var teamAccess = 'pull' // pull,push,admin options here +var teamId = '' + +var orgRepos = [] + +// var creator = "" + +var foo = someFunction(); +var bar = a + 1; + +http.createServer(function (req, res) { + handler(req, res, function (err) { + console.log(err) + res.statusCode = 404 + res.end('no such location') + }) +}).listen(3000) + +handler.on('error', function (err) { + console.await.error('Error:', err.message) +}) + +handler.on('repository', function (event) { + if (event.payload.action === 'created') { + const repo = event.payload.repository.full_name + console.log(repo) + const org = event.payload.repository.owner.login + getTeamID(org) + setTimeout(checkTeamIDVariable, 1000) + } +}) + +handler.on('team', function (event) { +// TODO user events such as being removed from team or org + if (event.payload.action === 'deleted') { + // const name = event.payload.team.name + const org = event.payload.organization.login + getRepositories(org) + setTimeout(checkReposVariable, 5000) + } else if (event.payload.action === 'removed_from_repository') { + const org = event.payload.organization.login + getTeamID(org) + // const repo = event.payload.repository.full_name + setTimeout(checkTeamIDVariable, 1000) + } +}) + +function getTeamID (org) { + const https = require('https') + + const options = { + hostname: (process.env.GHE_HOST), + port: 443 + path: '/api/v3/orgs/' + org + '/teams', + method: 'GET', + headers: { + Authorization: 'token ' + (process.env.GHE_TOKEN), + 'Content-Type': 'application/json' + } + } + let body = [] + const req = https.request(options, (res) => { + res.on('data', (chunk) => { + body.push(chunk) + }).on('end', () => { + body = JSON.parse(Buffer.concat(body)) + body.forEach(item => { + if (item.name === teamName) { + teamId = item.id + } + }) + }) + }) + + req.on('error, (error) => { + console.error(error) + }) + + req.end() +} + +function checkTeamIDVariable (repo) { + if (typeof teamId != 'undefined') { + addTeamToRepo(repo, teamId) + } +} + +function checkReposVariable (org) { + if (typeof orgRepos !== 'undefined') { + // for(var repo of orgRepos) { + // addTeamToRepo(repo, teamId) + // } + reCreateTeam(org) + } +} + +function addTeamToRepo (repo, teamId) { + const https = require('https') + const data = JSON.stringify({ + permission: teamAccess + }) + + const options = { + hostname: (process.env.GHE_HOST), + port: 443, + path: '/api/v3/teams/' + teamId + '/repos/' + repo, + method: 'PUT', + headers: { + Authorization: 'token ' + (process.env.GHE_TOKEN), + 'Content-Type': 'application/json', + 'Content-Length': data.length + } + } + let body = [] + + const req = https.request(options, (res) => { + res.on('data', (chunk) => { + + body.push(chunk) + + }).on('end', () => { + + body = Buffer.concat(body).toString() + console.log(res.statusCode) + console.log('added team to ' + repo) + }) + }) + + req.on('error', (error) => { + console.error(error) + }) + + req.write(data) + req.end() +} + +function reCreateTeam (org) { + const https = require('https') + const data = JSON.stringify({ + name: teamName, + description: teamDescription, + privacy: teamPrivacy + maintainers: userArray, + repo_names: orgRepos + }) + + const options = { + hostname: (process.env.GHE_HOST), + port: 443 + path: '/api/v3/orgs/' + org + '/teams', + method: 'POST', + headers: { + Authorization: 'token ' + (process.env.GHE_TOKEN), + 'Content-Type': 'application/json', + 'Content-Length': data.length + } + } + // const body = [] + const req = https.request(options, (res) => { + if (res.statusCode !== 201) { + console.log('Status code: ' + res.statusCode) + console.log('Added ' + teamName + ' to ' + org + ' Failed') + res.on('data', function (chunk) { + console.log('BODY: ' + chunk) + }) + } else { + console.log('Added ' + teamName ' to ' + org) + } + }) + + req.on('error', (error) => { + console.error(error) + }) + + req.write(data) + req.end() +} + +function getRepositories (org) { + orgRepos = [] + + const https = require('https') + + const options = { + hostname: (process.env.GHE_HOST), + port: '443', + path: '/api/v3/orgs/' + org + "/repos", + method: 'GET', + headers: { + Authorization: 'token ' + (process.env.GHE_TOKEN), + 'Content-Type': 'application/json' + } + } + let body = [] + const req = https.request(options, (res) => { + res.on('data', (chunk) => { + body.push(chunk) + + }).on('end', () => { + body = JSON.parse(Buffer.concat(body)) + body.forEach(item => { + orgRepos.push(item.full_name) + + console.log(item.full_name) + }) + }) + }) + + req.on('error', (error) => { + console.error(error) + }) + req.end() +} diff --git a/.automation/test/javascript_standard/javascript_good_1.js b/.automation/test/javascript_standard/javascript_good_1.js new file mode 100644 index 00000000..ad32089f --- /dev/null +++ b/.automation/test/javascript_standard/javascript_good_1.js @@ -0,0 +1,215 @@ +var http = require('http') +var createHandler = require('github-webhook-handler') +var handler = createHandler({ path: '/webhook', secret: (process.env.SECRET) }) + +var userArray = ['user1'] + +var teamDescription = 'Team of Robots' +var teamPrivacy = 'closed' // closed (visible) / secret (hidden) are options here + +var teamName = process.env.GHES_TEAM_NAME +var teamAccess = 'pull' // pull,push,admin options here +var teamId = '' + +var orgRepos = [] + +// var creator = "" + +http.createServer(function (req, res) { + handler(req, res, function (err) { + console.log(err) + res.statusCode = 404 + res.end('no such location') + }) +}).listen(3000) + +handler.on('error', function (err) { + console.error('Error:', err.message) +}) + +handler.on('repository', function (event) { + if (event.payload.action === 'created') { + const repo = event.payload.repository.full_name + console.log(repo) + const org = event.payload.repository.owner.login + getTeamID(org) + setTimeout(checkTeamIDVariable, 1000) + } +}) + +handler.on('team', function (event) { +// TODO user events such as being removed from team or org + if (event.payload.action === 'deleted') { + // const name = event.payload.team.name + const org = event.payload.organization.login + getRepositories(org) + setTimeout(checkReposVariable, 5000) + } else if (event.payload.action === 'removed_from_repository') { + const org = event.payload.organization.login + getTeamID(org) + // const repo = event.payload.repository.full_name + setTimeout(checkTeamIDVariable, 1000) + } +}) + +function getTeamID (org) { + const https = require('https') + + const options = { + hostname: (process.env.GHE_HOST), + port: 443, + path: '/api/v3/orgs/' + org + '/teams', + method: 'GET', + headers: { + Authorization: 'token ' + (process.env.GHE_TOKEN), + 'Content-Type': 'application/json' + } + } + let body = [] + const req = https.request(options, (res) => { + res.on('data', (chunk) => { + body.push(chunk) + }).on('end', () => { + body = JSON.parse(Buffer.concat(body)) + body.forEach(item => { + if (item.name === teamName) { + teamId = item.id + } + }) + }) + }) + + req.on('error', (error) => { + console.error(error) + }) + + req.end() +} + +function checkTeamIDVariable (repo) { + if (typeof teamId !== 'undefined') { + addTeamToRepo(repo, teamId) + } +} + +function checkReposVariable (org) { + if (typeof orgRepos !== 'undefined') { + // for(var repo of orgRepos) { + // addTeamToRepo(repo, teamId) + // } + reCreateTeam(org) + } +} + +function addTeamToRepo (repo, teamId) { + const https = require('https') + const data = JSON.stringify({ + permission: teamAccess + }) + + const options = { + hostname: (process.env.GHE_HOST), + port: 443, + path: '/api/v3/teams/' + teamId + '/repos/' + repo, + method: 'PUT', + headers: { + Authorization: 'token ' + (process.env.GHE_TOKEN), + 'Content-Type': 'application/json', + 'Content-Length': data.length + } + } + let body = [] + const req = https.request(options, (res) => { + res.on('data', (chunk) => { + body.push(chunk) + }).on('end', () => { + body = Buffer.concat(body).toString() + console.log(res.statusCode) + console.log('added team to ' + repo) + }) + }) + + req.on('error', (error) => { + console.error(error) + }) + + req.write(data) + req.end() +} + +function reCreateTeam (org) { + const https = require('https') + const data = JSON.stringify({ + name: teamName, + description: teamDescription, + privacy: teamPrivacy, + maintainers: userArray, + repo_names: orgRepos + }) + + const options = { + hostname: (process.env.GHE_HOST), + port: 443, + path: '/api/v3/orgs/' + org + '/teams', + method: 'POST', + headers: { + Authorization: 'token ' + (process.env.GHE_TOKEN), + 'Content-Type': 'application/json', + 'Content-Length': data.length + } + } + // const body = [] + const req = https.request(options, (res) => { + if (res.statusCode !== 201) { + console.log('Status code: ' + res.statusCode) + console.log('Added ' + teamName + ' to ' + org + ' Failed') + res.on('data', function (chunk) { + console.log('BODY: ' + chunk) + }) + } else { + console.log('Added ' + teamName + ' to ' + org) + } + }) + + req.on('error', (error) => { + console.error(error) + }) + + req.write(data) + req.end() +} + +function getRepositories (org) { + orgRepos = [] + + const https = require('https') + + const options = { + hostname: (process.env.GHE_HOST), + port: 443, + path: '/api/v3/orgs/' + org + '/repos', + method: 'GET', + headers: { + Authorization: 'token ' + (process.env.GHE_TOKEN), + 'Content-Type': 'application/json' + } + } + let body = [] + const req = https.request(options, (res) => { + res.on('data', (chunk) => { + body.push(chunk) + }).on('end', () => { + body = JSON.parse(Buffer.concat(body)) + body.forEach(item => { + orgRepos.push(item.full_name) + console.log(item.full_name) + }) + }) + }) + + req.on('error', (error) => { + console.error(error) + }) + + req.end() +} diff --git a/.automation/test/javascript_standard/reports/expected-JAVASCRIPT_ES.tap b/.automation/test/javascript_standard/reports/expected-JAVASCRIPT_ES.tap new file mode 100644 index 00000000..9e605d09 --- /dev/null +++ b/.automation/test/javascript_standard/reports/expected-JAVASCRIPT_ES.tap @@ -0,0 +1,7 @@ +TAP version 13 +1..2 +not ok 1 - javascript_bad_1.js + --- + message: \n/tmp/lint/.automation/test/javascript/javascript_bad_1.js\n 4 39 error Parsing error Unterminated regular expression literal\n\n✖ 1 problem (1 error, 0 warnings)\n + ... +ok 2 - javascript_good_1.js diff --git a/.automation/test/javascript_standard/reports/expected-JAVASCRIPT_STANDARD.tap b/.automation/test/javascript_standard/reports/expected-JAVASCRIPT_STANDARD.tap new file mode 100644 index 00000000..42d92b34 --- /dev/null +++ b/.automation/test/javascript_standard/reports/expected-JAVASCRIPT_STANDARD.tap @@ -0,0 +1,7 @@ +TAP version 13 +1..2 +not ok 1 - javascript_bad_1.js + --- + message: standard Use JavaScript Standard Style (https //standardjs.com)\n /tmp/lint/.automation/test/javascript/javascript_bad_1.js 4 40 Parsing error Unterminated regular expression\n + ... +ok 2 - javascript_good_1.js diff --git a/.automation/test/kubeval/README.md b/.automation/test/kubernetes_kubeval/README.md similarity index 100% rename from .automation/test/kubeval/README.md rename to .automation/test/kubernetes_kubeval/README.md diff --git a/.automation/test/kubeval/kubeval_bad_1.yaml b/.automation/test/kubernetes_kubeval/kubeval_bad_1.yaml similarity index 100% rename from .automation/test/kubeval/kubeval_bad_1.yaml rename to .automation/test/kubernetes_kubeval/kubeval_bad_1.yaml diff --git a/.automation/test/kubeval/kubeval_good_1.yaml b/.automation/test/kubernetes_kubeval/kubeval_good_1.yaml similarity index 100% rename from .automation/test/kubeval/kubeval_good_1.yaml rename to .automation/test/kubernetes_kubeval/kubeval_good_1.yaml diff --git a/.automation/test/php/README.md b/.automation/test/php_builtin/README.md similarity index 100% rename from .automation/test/php/README.md rename to .automation/test/php_builtin/README.md diff --git a/.automation/test/php/php_bad_1.php b/.automation/test/php_builtin/php_bad_1.php similarity index 100% rename from .automation/test/php/php_bad_1.php rename to .automation/test/php_builtin/php_bad_1.php diff --git a/.automation/test/php/php_bad_2.php b/.automation/test/php_builtin/php_bad_2.php similarity index 100% rename from .automation/test/php/php_bad_2.php rename to .automation/test/php_builtin/php_bad_2.php diff --git a/.automation/test/php/php_good_1.php b/.automation/test/php_builtin/php_good_1.php similarity index 100% rename from .automation/test/php/php_good_1.php rename to .automation/test/php_builtin/php_good_1.php diff --git a/.automation/test/php/php_good_2.php b/.automation/test/php_builtin/php_good_2.php similarity index 100% rename from .automation/test/php/php_good_2.php rename to .automation/test/php_builtin/php_good_2.php diff --git a/.automation/test/php/reports/expected-PHP_BUILTIN.tap b/.automation/test/php_builtin/reports/expected-PHP_BUILTIN.tap similarity index 100% rename from .automation/test/php/reports/expected-PHP_BUILTIN.tap rename to .automation/test/php_builtin/reports/expected-PHP_BUILTIN.tap diff --git a/.automation/test/php/reports/expected-PHP_PHPCS.tap.ignored b/.automation/test/php_builtin/reports/expected-PHP_PHPCS.tap.ignored similarity index 100% rename from .automation/test/php/reports/expected-PHP_PHPCS.tap.ignored rename to .automation/test/php_builtin/reports/expected-PHP_PHPCS.tap.ignored diff --git a/.automation/test/php/reports/expected-PHP_PHPSTAN.tap b/.automation/test/php_builtin/reports/expected-PHP_PHPSTAN.tap similarity index 100% rename from .automation/test/php/reports/expected-PHP_PHPSTAN.tap rename to .automation/test/php_builtin/reports/expected-PHP_PHPSTAN.tap diff --git a/.automation/test/php/reports/expected-PHP_PSALM.tap.ignored b/.automation/test/php_builtin/reports/expected-PHP_PSALM.tap.ignored similarity index 100% rename from .automation/test/php/reports/expected-PHP_PSALM.tap.ignored rename to .automation/test/php_builtin/reports/expected-PHP_PSALM.tap.ignored diff --git a/.automation/test/php_phpcs/README.md b/.automation/test/php_phpcs/README.md new file mode 100644 index 00000000..e1156daf --- /dev/null +++ b/.automation/test/php_phpcs/README.md @@ -0,0 +1,19 @@ +# PHP Test Cases + +This folder holds the test cases for **PHP**. + +## Additional Docs + +No Additional information is needed for this test case. + +## Good Test Cases + +The test cases denoted: `LANGUAGE_good_FILE.EXTENSION` are all valid, and should pass successfully when linted. + +- **Note:** They are linted utilizing the default linter rules. + +## Bad Test Cases + +The test cases denoted: `LANGUAGE_bad_FILE.EXTENSION` are **NOT** valid, and should trigger errors when linted. + +- **Note:** They are linted utilizing the default linter rules. diff --git a/.automation/test/php_phpcs/php_bad_1.php b/.automation/test/php_phpcs/php_bad_1.php new file mode 100644 index 00000000..e564fc66 --- /dev/null +++ b/.automation/test/php_phpcs/php_bad_1.php @@ -0,0 +1,3 @@ + + */ +function takesAnInt(int $i) { + return [$i, "hello"]; +} + +$data = ["some text", 5]; +takesAnInt($data[0]); + +$condition = rand(0, 5); +iff ($condition) { +} elseif ($condition) {} diff --git a/.automation/test/php_phpcs/php_good_1.php b/.automation/test/php_phpcs/php_good_1.php new file mode 100644 index 00000000..16898d93 --- /dev/null +++ b/.automation/test/php_phpcs/php_good_1.php @@ -0,0 +1,3 @@ + + */ +function helloName(string $name): array +{ + return ["hello", $name]; +} + +function helloSuperLinter(): void +{ + $hello = helloName("Super-Linter"); + echo implode(" ", $hello) . PHP_EOL; +} + +function helloOrWorld(): void +{ + $random = rand(0, 10); + if ($random >= 5) { + echo "Hello"; + } else { + echo "World"; + } +} diff --git a/.automation/test/php_phpcs/reports/expected-PHP_BUILTIN.tap b/.automation/test/php_phpcs/reports/expected-PHP_BUILTIN.tap new file mode 100644 index 00000000..beb3656e --- /dev/null +++ b/.automation/test/php_phpcs/reports/expected-PHP_BUILTIN.tap @@ -0,0 +1,12 @@ +TAP version 13 +1..4 +not ok 1 - php_bad_1.php + --- + message: PHP Parse error syntax error, unexpected 'pe98y' (T_STRING) in /tmp/lint/.automation/test/php/php_bad_1.php on line 3\nErrors parsing /tmp/lint/.automation/test/php/php_bad_1.php\n + ... +not ok 2 - php_bad_2.php + --- + message: PHP Parse error syntax error, unexpected '}' in /tmp/lint/.automation/test/php/php_bad_2.php on line 15\nErrors parsing /tmp/lint/.automation/test/php/php_bad_2.php\n + ... +ok 3 - php_good_1.php +ok 4 - php_good_2.php diff --git a/.automation/test/php_phpcs/reports/expected-PHP_PHPCS.tap.ignored b/.automation/test/php_phpcs/reports/expected-PHP_PHPCS.tap.ignored new file mode 100644 index 00000000..bf7e541d --- /dev/null +++ b/.automation/test/php_phpcs/reports/expected-PHP_PHPCS.tap.ignored @@ -0,0 +1,12 @@ +TAP version 13 +1..4 +not ok 1 - php_bad_1.php + --- + message: \nFILE /tmp/lint/.automation/test/php/php_bad_1.php\n----------------------------------------------------------------------\nFOUND 7 ERRORS AFFECTING 1 LINE\n----------------------------------------------------------------------\n 3 | ERROR | [x] Expected at least 1 space before "-"; 0 found\n 3 | ERROR | [x] Expected at least 1 space after "-"; 0 found\n 3 | ERROR | [x] Expected at least 1 space before "="; 0 found\n 3 | ERROR | [x] Expected at least 1 space after "="; 0 found\n 3 | ERROR | [x] Expected at least 1 space before "-"; 0 found\n 3 | ERROR | [x] Expected at least 1 space before "-"; 0 found\n 3 | ERROR | [x] Expected at least 1 space after "-"; 0 found\n----------------------------------------------------------------------\nPHPCBF CAN FIX THE 7 MARKED SNIFF VIOLATIONS AUTOMATICALLY\n----------------------------------------------------------------------\n\n + ... +not ok 2 - php_bad_2.php + --- + message: \nFILE /tmp/lint/.automation/test/php/php_bad_2.php\n----------------------------------------------------------------------\nFOUND 4 ERRORS AND 1 WARNING AFFECTING 4 LINES\n----------------------------------------------------------------------\n 1 | WARNING | [ ] A file should declare new symbols (classes,\n | | functions, constants, etc.) and cause no other\n | | side effects, or it should execute logic with\n | | side effects, but should not do both. The first\n | | symbol is defined on line 6 and the first side\n | | effect is on line 10.\n 6 | ERROR | [x] Opening brace should be on a new line\n 14 | ERROR | [x] Space before opening parenthesis of function call\n | | prohibited\n 15 | ERROR | [x] Newline required after opening brace\n 15 | ERROR | [x] Closing brace must be on a line by itself\n----------------------------------------------------------------------\nPHPCBF CAN FIX THE 4 MARKED SNIFF VIOLATIONS AUTOMATICALLY\n----------------------------------------------------------------------\n\n + ... +ok 3 - php_good_1.php +ok 4 - php_good_2.php diff --git a/.automation/test/php_phpcs/reports/expected-PHP_PHPSTAN.tap b/.automation/test/php_phpcs/reports/expected-PHP_PHPSTAN.tap new file mode 100644 index 00000000..03f22dd1 --- /dev/null +++ b/.automation/test/php_phpcs/reports/expected-PHP_PHPSTAN.tap @@ -0,0 +1,12 @@ +TAP version 13 +1..4 +not ok 1 - php_bad_1.php + --- + message: ------ ---------------------------------------------- \n Line php_bad_1.php \n ------ ---------------------------------------------- \n 3 Invalid numeric literal on line 3 \n 3 Invalid numeric literal on line 3 \n 3 Syntax error, unexpected '=' on line 3 \n 3 Syntax error, unexpected T_LNUMBER on line 3 \n 3 Syntax error, unexpected T_STRING on line 3 \n 3 Syntax error, unexpected T_STRING on line 3 \n ------ ---------------------------------------------- \n\n [ERROR] Found 6 errors \n + ... +not ok 2 - php_bad_2.php + --- + message: ------ ----------------------------------------- \n Line php_bad_2.php \n ------ ----------------------------------------- \n 15 Syntax error, unexpected '}' on line 15 \n ------ ----------------------------------------- \n\n [ERROR] Found 1 error \n + ... +ok 3 - php_good_1.php +ok 4 - php_good_2.php diff --git a/.automation/test/php_phpcs/reports/expected-PHP_PSALM.tap.ignored b/.automation/test/php_phpcs/reports/expected-PHP_PSALM.tap.ignored new file mode 100644 index 00000000..57baa89a --- /dev/null +++ b/.automation/test/php_phpcs/reports/expected-PHP_PSALM.tap.ignored @@ -0,0 +1,12 @@ +TAP version 13 +1..4 +not ok 1 - php_bad_1.php + --- + message: Scanning files...\nAnalyzing files...\n\nE\n\nERROR ParseError - php/php_bad_1.php 3 2 - Syntax error, unexpected T_STRING on line 3 (see https //psalm.dev/173)\n2pe98y r-n0u823n=r 092u3- r08u2q098ry 09nq2yr09n2yr9 y2n-93yr 298yr3 29\n\n\nERROR UndefinedConstant - php/php_bad_1.php 3 2 - Const pe98y is not defined (see https //psalm.dev/020)\n2pe98y r-n0u823n=r 092u3- r08u2q098ry 09nq2yr09n2yr9 y2n-93yr 298yr3 29\n\n\nERROR UndefinedConstant - php/php_bad_1.php 3 8 - Const r is not defined (see https //psalm.dev/020)\n2pe98y r-n0u823n=r 092u3- r08u2q098ry 09nq2yr09n2yr9 y2n-93yr 298yr3 29\n\n\nERROR UndefinedConstant - php/php_bad_1.php 3 10 - Const n0u823n is not defined (see https //psalm.dev/020)\n2pe98y r-n0u823n=r 092u3- r08u2q098ry 09nq2yr09n2yr9 y2n-93yr 298yr3 29\n\n\nERROR ParseError - php/php_bad_1.php 3 17 - Syntax error, unexpected '=' on line 3 (see https //psalm.dev/173)\n2pe98y r-n0u823n=r 092u3- r08u2q098ry 09nq2yr09n2yr9 y2n-93yr 298yr3 29\n\n\nERROR UndefinedConstant - php/php_bad_1.php 3 18 - Const r is not defined (see https //psalm.dev/020)\n2pe98y r-n0u823n=r 092u3- r08u2q098ry 09nq2yr09n2yr9 y2n-93yr 298yr3 29\n\n\nERROR ParseError - php/php_bad_1.php 3 21 - Invalid numeric literal on line 3 (see https //psalm.dev/173)\n2pe98y r-n0u823n=r 092u3- r08u2q098ry 09nq2yr09n2yr9 y2n-93yr 298yr3 29\n\n\nERROR UndefinedConstant - php/php_bad_1.php 3 24 - Const u3 is not defined (see https //psalm.dev/020)\n2pe98y r-n0u823n=r 092u3- r08u2q098ry 09nq2yr09n2yr9 y2n-93yr 298yr3 29\n\n\nERROR UndefinedConstant - php/php_bad_1.php 3 28 - Const r08u2q098ry is not defined (see https //psalm.dev/020)\n2pe98y r-n0u823n=r 092u3- r08u2q098ry 09nq2yr09n2yr9 y2n-93yr 298yr3 29\n\n\nERROR ParseError - php/php_bad_1.php 3 40 - Syntax error, unexpected T_LNUMBER on line 3 (see https //psalm.dev/173)\n2pe98y r-n0u823n=r 092u3- r08u2q098ry 09nq2yr09n2yr9 y2n-93yr 298yr3 29\n\n\nERROR UndefinedConstant - php/php_bad_1.php 3 42 - Const nq2yr09n2yr9 is not defined (see https //psalm.dev/020)\n2pe98y r-n0u823n=r 092u3- r08u2q098ry 09nq2yr09n2yr9 y2n-93yr 298yr3 29\n\n\nERROR UndefinedConstant - php/php_bad_1.php 3 55 - Const y2n is not defined (see https //psalm.dev/020)\n2pe98y r-n0u823n=r 092u3- r08u2q098ry 09nq2yr09n2yr9 y2n-93yr 298yr3 29\n\n\nERROR ParseError - php/php_bad_1.php 3 61 - Syntax error, unexpected T_STRING on line 3 (see https //psalm.dev/173)\n2pe98y r-n0u823n=r 092u3- r08u2q098ry 09nq2yr09n2yr9 y2n-93yr 298yr3 29\n\n\nERROR UndefinedConstant - php/php_bad_1.php 3 61 - Const yr is not defined (see https //psalm.dev/020)\n2pe98y r-n0u823n=r 092u3- r08u2q098ry 09nq2yr09n2yr9 y2n-93yr 298yr3 29\n\n\nERROR UndefinedConstant - php/php_bad_1.php 3 68 - Const yr3 is not defined (see https //psalm.dev/020)\n2pe98y r-n0u823n=r 092u3- r08u2q098ry 09nq2yr09n2yr9 y2n-93yr 298yr3 29\n\n\n------------------------------\n15 errors found\n------------------------------\n\n + ... +not ok 2 - php_bad_2.php + --- + message: Scanning files...\nAnalyzing files...\n\nE\n\nERROR InvalidReturnType - php/php_bad_2.php 4 12 - The declared return type 'array' for takesAnInt is incorrect, got 'array{int, string(hello)}' (see https //psalm.dev/011)\n * @return array\n\n\nERROR InvalidReturnStatement - php/php_bad_2.php 7 12 - The inferred type 'array{int, string(hello)}' does not match the declared return type 'array' for takesAnInt (see https //psalm.dev/128)\n return [$i, "hello"];\n\n\nERROR InvalidScalarArgument - php/php_bad_2.php 11 12 - Argument 1 of takesAnInt expects int, string(some text) provided (see https //psalm.dev/012)\ntakesAnInt($data[0]);\n\n\nERROR ParseError - php/php_bad_2.php 15 1 - Syntax error, unexpected '}' on line 15 (see https //psalm.dev/173)\n} elseif ($condition) {}\n\n\n------------------------------\n4 errors found\n------------------------------\nPsalm can automatically fix 1 of these issues.\nRun Psalm again with \n--alter --issues=InvalidReturnType --dry-run\nto see what it can fix.\n------------------------------\n\n + ... +ok 3 - php_good_1.php +ok 4 - php_good_2.php diff --git a/.automation/test/php_phpstan/README.md b/.automation/test/php_phpstan/README.md new file mode 100644 index 00000000..e1156daf --- /dev/null +++ b/.automation/test/php_phpstan/README.md @@ -0,0 +1,19 @@ +# PHP Test Cases + +This folder holds the test cases for **PHP**. + +## Additional Docs + +No Additional information is needed for this test case. + +## Good Test Cases + +The test cases denoted: `LANGUAGE_good_FILE.EXTENSION` are all valid, and should pass successfully when linted. + +- **Note:** They are linted utilizing the default linter rules. + +## Bad Test Cases + +The test cases denoted: `LANGUAGE_bad_FILE.EXTENSION` are **NOT** valid, and should trigger errors when linted. + +- **Note:** They are linted utilizing the default linter rules. diff --git a/.automation/test/php_phpstan/php_bad_1.php b/.automation/test/php_phpstan/php_bad_1.php new file mode 100644 index 00000000..e564fc66 --- /dev/null +++ b/.automation/test/php_phpstan/php_bad_1.php @@ -0,0 +1,3 @@ + + */ +function takesAnInt(int $i) { + return [$i, "hello"]; +} + +$data = ["some text", 5]; +takesAnInt($data[0]); + +$condition = rand(0, 5); +iff ($condition) { +} elseif ($condition) {} diff --git a/.automation/test/php_phpstan/php_good_1.php b/.automation/test/php_phpstan/php_good_1.php new file mode 100644 index 00000000..16898d93 --- /dev/null +++ b/.automation/test/php_phpstan/php_good_1.php @@ -0,0 +1,3 @@ + + */ +function helloName(string $name): array +{ + return ["hello", $name]; +} + +function helloSuperLinter(): void +{ + $hello = helloName("Super-Linter"); + echo implode(" ", $hello) . PHP_EOL; +} + +function helloOrWorld(): void +{ + $random = rand(0, 10); + if ($random >= 5) { + echo "Hello"; + } else { + echo "World"; + } +} diff --git a/.automation/test/php_phpstan/reports/expected-PHP_BUILTIN.tap b/.automation/test/php_phpstan/reports/expected-PHP_BUILTIN.tap new file mode 100644 index 00000000..beb3656e --- /dev/null +++ b/.automation/test/php_phpstan/reports/expected-PHP_BUILTIN.tap @@ -0,0 +1,12 @@ +TAP version 13 +1..4 +not ok 1 - php_bad_1.php + --- + message: PHP Parse error syntax error, unexpected 'pe98y' (T_STRING) in /tmp/lint/.automation/test/php/php_bad_1.php on line 3\nErrors parsing /tmp/lint/.automation/test/php/php_bad_1.php\n + ... +not ok 2 - php_bad_2.php + --- + message: PHP Parse error syntax error, unexpected '}' in /tmp/lint/.automation/test/php/php_bad_2.php on line 15\nErrors parsing /tmp/lint/.automation/test/php/php_bad_2.php\n + ... +ok 3 - php_good_1.php +ok 4 - php_good_2.php diff --git a/.automation/test/php_phpstan/reports/expected-PHP_PHPCS.tap.ignored b/.automation/test/php_phpstan/reports/expected-PHP_PHPCS.tap.ignored new file mode 100644 index 00000000..bf7e541d --- /dev/null +++ b/.automation/test/php_phpstan/reports/expected-PHP_PHPCS.tap.ignored @@ -0,0 +1,12 @@ +TAP version 13 +1..4 +not ok 1 - php_bad_1.php + --- + message: \nFILE /tmp/lint/.automation/test/php/php_bad_1.php\n----------------------------------------------------------------------\nFOUND 7 ERRORS AFFECTING 1 LINE\n----------------------------------------------------------------------\n 3 | ERROR | [x] Expected at least 1 space before "-"; 0 found\n 3 | ERROR | [x] Expected at least 1 space after "-"; 0 found\n 3 | ERROR | [x] Expected at least 1 space before "="; 0 found\n 3 | ERROR | [x] Expected at least 1 space after "="; 0 found\n 3 | ERROR | [x] Expected at least 1 space before "-"; 0 found\n 3 | ERROR | [x] Expected at least 1 space before "-"; 0 found\n 3 | ERROR | [x] Expected at least 1 space after "-"; 0 found\n----------------------------------------------------------------------\nPHPCBF CAN FIX THE 7 MARKED SNIFF VIOLATIONS AUTOMATICALLY\n----------------------------------------------------------------------\n\n + ... +not ok 2 - php_bad_2.php + --- + message: \nFILE /tmp/lint/.automation/test/php/php_bad_2.php\n----------------------------------------------------------------------\nFOUND 4 ERRORS AND 1 WARNING AFFECTING 4 LINES\n----------------------------------------------------------------------\n 1 | WARNING | [ ] A file should declare new symbols (classes,\n | | functions, constants, etc.) and cause no other\n | | side effects, or it should execute logic with\n | | side effects, but should not do both. The first\n | | symbol is defined on line 6 and the first side\n | | effect is on line 10.\n 6 | ERROR | [x] Opening brace should be on a new line\n 14 | ERROR | [x] Space before opening parenthesis of function call\n | | prohibited\n 15 | ERROR | [x] Newline required after opening brace\n 15 | ERROR | [x] Closing brace must be on a line by itself\n----------------------------------------------------------------------\nPHPCBF CAN FIX THE 4 MARKED SNIFF VIOLATIONS AUTOMATICALLY\n----------------------------------------------------------------------\n\n + ... +ok 3 - php_good_1.php +ok 4 - php_good_2.php diff --git a/.automation/test/php_phpstan/reports/expected-PHP_PHPSTAN.tap b/.automation/test/php_phpstan/reports/expected-PHP_PHPSTAN.tap new file mode 100644 index 00000000..03f22dd1 --- /dev/null +++ b/.automation/test/php_phpstan/reports/expected-PHP_PHPSTAN.tap @@ -0,0 +1,12 @@ +TAP version 13 +1..4 +not ok 1 - php_bad_1.php + --- + message: ------ ---------------------------------------------- \n Line php_bad_1.php \n ------ ---------------------------------------------- \n 3 Invalid numeric literal on line 3 \n 3 Invalid numeric literal on line 3 \n 3 Syntax error, unexpected '=' on line 3 \n 3 Syntax error, unexpected T_LNUMBER on line 3 \n 3 Syntax error, unexpected T_STRING on line 3 \n 3 Syntax error, unexpected T_STRING on line 3 \n ------ ---------------------------------------------- \n\n [ERROR] Found 6 errors \n + ... +not ok 2 - php_bad_2.php + --- + message: ------ ----------------------------------------- \n Line php_bad_2.php \n ------ ----------------------------------------- \n 15 Syntax error, unexpected '}' on line 15 \n ------ ----------------------------------------- \n\n [ERROR] Found 1 error \n + ... +ok 3 - php_good_1.php +ok 4 - php_good_2.php diff --git a/.automation/test/php_phpstan/reports/expected-PHP_PSALM.tap.ignored b/.automation/test/php_phpstan/reports/expected-PHP_PSALM.tap.ignored new file mode 100644 index 00000000..57baa89a --- /dev/null +++ b/.automation/test/php_phpstan/reports/expected-PHP_PSALM.tap.ignored @@ -0,0 +1,12 @@ +TAP version 13 +1..4 +not ok 1 - php_bad_1.php + --- + message: Scanning files...\nAnalyzing files...\n\nE\n\nERROR ParseError - php/php_bad_1.php 3 2 - Syntax error, unexpected T_STRING on line 3 (see https //psalm.dev/173)\n2pe98y r-n0u823n=r 092u3- r08u2q098ry 09nq2yr09n2yr9 y2n-93yr 298yr3 29\n\n\nERROR UndefinedConstant - php/php_bad_1.php 3 2 - Const pe98y is not defined (see https //psalm.dev/020)\n2pe98y r-n0u823n=r 092u3- r08u2q098ry 09nq2yr09n2yr9 y2n-93yr 298yr3 29\n\n\nERROR UndefinedConstant - php/php_bad_1.php 3 8 - Const r is not defined (see https //psalm.dev/020)\n2pe98y r-n0u823n=r 092u3- r08u2q098ry 09nq2yr09n2yr9 y2n-93yr 298yr3 29\n\n\nERROR UndefinedConstant - php/php_bad_1.php 3 10 - Const n0u823n is not defined (see https //psalm.dev/020)\n2pe98y r-n0u823n=r 092u3- r08u2q098ry 09nq2yr09n2yr9 y2n-93yr 298yr3 29\n\n\nERROR ParseError - php/php_bad_1.php 3 17 - Syntax error, unexpected '=' on line 3 (see https //psalm.dev/173)\n2pe98y r-n0u823n=r 092u3- r08u2q098ry 09nq2yr09n2yr9 y2n-93yr 298yr3 29\n\n\nERROR UndefinedConstant - php/php_bad_1.php 3 18 - Const r is not defined (see https //psalm.dev/020)\n2pe98y r-n0u823n=r 092u3- r08u2q098ry 09nq2yr09n2yr9 y2n-93yr 298yr3 29\n\n\nERROR ParseError - php/php_bad_1.php 3 21 - Invalid numeric literal on line 3 (see https //psalm.dev/173)\n2pe98y r-n0u823n=r 092u3- r08u2q098ry 09nq2yr09n2yr9 y2n-93yr 298yr3 29\n\n\nERROR UndefinedConstant - php/php_bad_1.php 3 24 - Const u3 is not defined (see https //psalm.dev/020)\n2pe98y r-n0u823n=r 092u3- r08u2q098ry 09nq2yr09n2yr9 y2n-93yr 298yr3 29\n\n\nERROR UndefinedConstant - php/php_bad_1.php 3 28 - Const r08u2q098ry is not defined (see https //psalm.dev/020)\n2pe98y r-n0u823n=r 092u3- r08u2q098ry 09nq2yr09n2yr9 y2n-93yr 298yr3 29\n\n\nERROR ParseError - php/php_bad_1.php 3 40 - Syntax error, unexpected T_LNUMBER on line 3 (see https //psalm.dev/173)\n2pe98y r-n0u823n=r 092u3- r08u2q098ry 09nq2yr09n2yr9 y2n-93yr 298yr3 29\n\n\nERROR UndefinedConstant - php/php_bad_1.php 3 42 - Const nq2yr09n2yr9 is not defined (see https //psalm.dev/020)\n2pe98y r-n0u823n=r 092u3- r08u2q098ry 09nq2yr09n2yr9 y2n-93yr 298yr3 29\n\n\nERROR UndefinedConstant - php/php_bad_1.php 3 55 - Const y2n is not defined (see https //psalm.dev/020)\n2pe98y r-n0u823n=r 092u3- r08u2q098ry 09nq2yr09n2yr9 y2n-93yr 298yr3 29\n\n\nERROR ParseError - php/php_bad_1.php 3 61 - Syntax error, unexpected T_STRING on line 3 (see https //psalm.dev/173)\n2pe98y r-n0u823n=r 092u3- r08u2q098ry 09nq2yr09n2yr9 y2n-93yr 298yr3 29\n\n\nERROR UndefinedConstant - php/php_bad_1.php 3 61 - Const yr is not defined (see https //psalm.dev/020)\n2pe98y r-n0u823n=r 092u3- r08u2q098ry 09nq2yr09n2yr9 y2n-93yr 298yr3 29\n\n\nERROR UndefinedConstant - php/php_bad_1.php 3 68 - Const yr3 is not defined (see https //psalm.dev/020)\n2pe98y r-n0u823n=r 092u3- r08u2q098ry 09nq2yr09n2yr9 y2n-93yr 298yr3 29\n\n\n------------------------------\n15 errors found\n------------------------------\n\n + ... +not ok 2 - php_bad_2.php + --- + message: Scanning files...\nAnalyzing files...\n\nE\n\nERROR InvalidReturnType - php/php_bad_2.php 4 12 - The declared return type 'array' for takesAnInt is incorrect, got 'array{int, string(hello)}' (see https //psalm.dev/011)\n * @return array\n\n\nERROR InvalidReturnStatement - php/php_bad_2.php 7 12 - The inferred type 'array{int, string(hello)}' does not match the declared return type 'array' for takesAnInt (see https //psalm.dev/128)\n return [$i, "hello"];\n\n\nERROR InvalidScalarArgument - php/php_bad_2.php 11 12 - Argument 1 of takesAnInt expects int, string(some text) provided (see https //psalm.dev/012)\ntakesAnInt($data[0]);\n\n\nERROR ParseError - php/php_bad_2.php 15 1 - Syntax error, unexpected '}' on line 15 (see https //psalm.dev/173)\n} elseif ($condition) {}\n\n\n------------------------------\n4 errors found\n------------------------------\nPsalm can automatically fix 1 of these issues.\nRun Psalm again with \n--alter --issues=InvalidReturnType --dry-run\nto see what it can fix.\n------------------------------\n\n + ... +ok 3 - php_good_1.php +ok 4 - php_good_2.php diff --git a/.automation/test/php_psalm/README.md b/.automation/test/php_psalm/README.md new file mode 100644 index 00000000..e1156daf --- /dev/null +++ b/.automation/test/php_psalm/README.md @@ -0,0 +1,19 @@ +# PHP Test Cases + +This folder holds the test cases for **PHP**. + +## Additional Docs + +No Additional information is needed for this test case. + +## Good Test Cases + +The test cases denoted: `LANGUAGE_good_FILE.EXTENSION` are all valid, and should pass successfully when linted. + +- **Note:** They are linted utilizing the default linter rules. + +## Bad Test Cases + +The test cases denoted: `LANGUAGE_bad_FILE.EXTENSION` are **NOT** valid, and should trigger errors when linted. + +- **Note:** They are linted utilizing the default linter rules. diff --git a/.automation/test/php_psalm/php_bad_1.php b/.automation/test/php_psalm/php_bad_1.php new file mode 100644 index 00000000..e564fc66 --- /dev/null +++ b/.automation/test/php_psalm/php_bad_1.php @@ -0,0 +1,3 @@ + + */ +function takesAnInt(int $i) { + return [$i, "hello"]; +} + +$data = ["some text", 5]; +takesAnInt($data[0]); + +$condition = rand(0, 5); +iff ($condition) { +} elseif ($condition) {} diff --git a/.automation/test/php_psalm/php_good_1.php b/.automation/test/php_psalm/php_good_1.php new file mode 100644 index 00000000..16898d93 --- /dev/null +++ b/.automation/test/php_psalm/php_good_1.php @@ -0,0 +1,3 @@ + + */ +function helloName(string $name): array +{ + return ["hello", $name]; +} + +function helloSuperLinter(): void +{ + $hello = helloName("Super-Linter"); + echo implode(" ", $hello) . PHP_EOL; +} + +function helloOrWorld(): void +{ + $random = rand(0, 10); + if ($random >= 5) { + echo "Hello"; + } else { + echo "World"; + } +} diff --git a/.automation/test/php_psalm/reports/expected-PHP_BUILTIN.tap b/.automation/test/php_psalm/reports/expected-PHP_BUILTIN.tap new file mode 100644 index 00000000..beb3656e --- /dev/null +++ b/.automation/test/php_psalm/reports/expected-PHP_BUILTIN.tap @@ -0,0 +1,12 @@ +TAP version 13 +1..4 +not ok 1 - php_bad_1.php + --- + message: PHP Parse error syntax error, unexpected 'pe98y' (T_STRING) in /tmp/lint/.automation/test/php/php_bad_1.php on line 3\nErrors parsing /tmp/lint/.automation/test/php/php_bad_1.php\n + ... +not ok 2 - php_bad_2.php + --- + message: PHP Parse error syntax error, unexpected '}' in /tmp/lint/.automation/test/php/php_bad_2.php on line 15\nErrors parsing /tmp/lint/.automation/test/php/php_bad_2.php\n + ... +ok 3 - php_good_1.php +ok 4 - php_good_2.php diff --git a/.automation/test/php_psalm/reports/expected-PHP_PHPCS.tap.ignored b/.automation/test/php_psalm/reports/expected-PHP_PHPCS.tap.ignored new file mode 100644 index 00000000..bf7e541d --- /dev/null +++ b/.automation/test/php_psalm/reports/expected-PHP_PHPCS.tap.ignored @@ -0,0 +1,12 @@ +TAP version 13 +1..4 +not ok 1 - php_bad_1.php + --- + message: \nFILE /tmp/lint/.automation/test/php/php_bad_1.php\n----------------------------------------------------------------------\nFOUND 7 ERRORS AFFECTING 1 LINE\n----------------------------------------------------------------------\n 3 | ERROR | [x] Expected at least 1 space before "-"; 0 found\n 3 | ERROR | [x] Expected at least 1 space after "-"; 0 found\n 3 | ERROR | [x] Expected at least 1 space before "="; 0 found\n 3 | ERROR | [x] Expected at least 1 space after "="; 0 found\n 3 | ERROR | [x] Expected at least 1 space before "-"; 0 found\n 3 | ERROR | [x] Expected at least 1 space before "-"; 0 found\n 3 | ERROR | [x] Expected at least 1 space after "-"; 0 found\n----------------------------------------------------------------------\nPHPCBF CAN FIX THE 7 MARKED SNIFF VIOLATIONS AUTOMATICALLY\n----------------------------------------------------------------------\n\n + ... +not ok 2 - php_bad_2.php + --- + message: \nFILE /tmp/lint/.automation/test/php/php_bad_2.php\n----------------------------------------------------------------------\nFOUND 4 ERRORS AND 1 WARNING AFFECTING 4 LINES\n----------------------------------------------------------------------\n 1 | WARNING | [ ] A file should declare new symbols (classes,\n | | functions, constants, etc.) and cause no other\n | | side effects, or it should execute logic with\n | | side effects, but should not do both. The first\n | | symbol is defined on line 6 and the first side\n | | effect is on line 10.\n 6 | ERROR | [x] Opening brace should be on a new line\n 14 | ERROR | [x] Space before opening parenthesis of function call\n | | prohibited\n 15 | ERROR | [x] Newline required after opening brace\n 15 | ERROR | [x] Closing brace must be on a line by itself\n----------------------------------------------------------------------\nPHPCBF CAN FIX THE 4 MARKED SNIFF VIOLATIONS AUTOMATICALLY\n----------------------------------------------------------------------\n\n + ... +ok 3 - php_good_1.php +ok 4 - php_good_2.php diff --git a/.automation/test/php_psalm/reports/expected-PHP_PHPSTAN.tap b/.automation/test/php_psalm/reports/expected-PHP_PHPSTAN.tap new file mode 100644 index 00000000..03f22dd1 --- /dev/null +++ b/.automation/test/php_psalm/reports/expected-PHP_PHPSTAN.tap @@ -0,0 +1,12 @@ +TAP version 13 +1..4 +not ok 1 - php_bad_1.php + --- + message: ------ ---------------------------------------------- \n Line php_bad_1.php \n ------ ---------------------------------------------- \n 3 Invalid numeric literal on line 3 \n 3 Invalid numeric literal on line 3 \n 3 Syntax error, unexpected '=' on line 3 \n 3 Syntax error, unexpected T_LNUMBER on line 3 \n 3 Syntax error, unexpected T_STRING on line 3 \n 3 Syntax error, unexpected T_STRING on line 3 \n ------ ---------------------------------------------- \n\n [ERROR] Found 6 errors \n + ... +not ok 2 - php_bad_2.php + --- + message: ------ ----------------------------------------- \n Line php_bad_2.php \n ------ ----------------------------------------- \n 15 Syntax error, unexpected '}' on line 15 \n ------ ----------------------------------------- \n\n [ERROR] Found 1 error \n + ... +ok 3 - php_good_1.php +ok 4 - php_good_2.php diff --git a/.automation/test/php_psalm/reports/expected-PHP_PSALM.tap.ignored b/.automation/test/php_psalm/reports/expected-PHP_PSALM.tap.ignored new file mode 100644 index 00000000..57baa89a --- /dev/null +++ b/.automation/test/php_psalm/reports/expected-PHP_PSALM.tap.ignored @@ -0,0 +1,12 @@ +TAP version 13 +1..4 +not ok 1 - php_bad_1.php + --- + message: Scanning files...\nAnalyzing files...\n\nE\n\nERROR ParseError - php/php_bad_1.php 3 2 - Syntax error, unexpected T_STRING on line 3 (see https //psalm.dev/173)\n2pe98y r-n0u823n=r 092u3- r08u2q098ry 09nq2yr09n2yr9 y2n-93yr 298yr3 29\n\n\nERROR UndefinedConstant - php/php_bad_1.php 3 2 - Const pe98y is not defined (see https //psalm.dev/020)\n2pe98y r-n0u823n=r 092u3- r08u2q098ry 09nq2yr09n2yr9 y2n-93yr 298yr3 29\n\n\nERROR UndefinedConstant - php/php_bad_1.php 3 8 - Const r is not defined (see https //psalm.dev/020)\n2pe98y r-n0u823n=r 092u3- r08u2q098ry 09nq2yr09n2yr9 y2n-93yr 298yr3 29\n\n\nERROR UndefinedConstant - php/php_bad_1.php 3 10 - Const n0u823n is not defined (see https //psalm.dev/020)\n2pe98y r-n0u823n=r 092u3- r08u2q098ry 09nq2yr09n2yr9 y2n-93yr 298yr3 29\n\n\nERROR ParseError - php/php_bad_1.php 3 17 - Syntax error, unexpected '=' on line 3 (see https //psalm.dev/173)\n2pe98y r-n0u823n=r 092u3- r08u2q098ry 09nq2yr09n2yr9 y2n-93yr 298yr3 29\n\n\nERROR UndefinedConstant - php/php_bad_1.php 3 18 - Const r is not defined (see https //psalm.dev/020)\n2pe98y r-n0u823n=r 092u3- r08u2q098ry 09nq2yr09n2yr9 y2n-93yr 298yr3 29\n\n\nERROR ParseError - php/php_bad_1.php 3 21 - Invalid numeric literal on line 3 (see https //psalm.dev/173)\n2pe98y r-n0u823n=r 092u3- r08u2q098ry 09nq2yr09n2yr9 y2n-93yr 298yr3 29\n\n\nERROR UndefinedConstant - php/php_bad_1.php 3 24 - Const u3 is not defined (see https //psalm.dev/020)\n2pe98y r-n0u823n=r 092u3- r08u2q098ry 09nq2yr09n2yr9 y2n-93yr 298yr3 29\n\n\nERROR UndefinedConstant - php/php_bad_1.php 3 28 - Const r08u2q098ry is not defined (see https //psalm.dev/020)\n2pe98y r-n0u823n=r 092u3- r08u2q098ry 09nq2yr09n2yr9 y2n-93yr 298yr3 29\n\n\nERROR ParseError - php/php_bad_1.php 3 40 - Syntax error, unexpected T_LNUMBER on line 3 (see https //psalm.dev/173)\n2pe98y r-n0u823n=r 092u3- r08u2q098ry 09nq2yr09n2yr9 y2n-93yr 298yr3 29\n\n\nERROR UndefinedConstant - php/php_bad_1.php 3 42 - Const nq2yr09n2yr9 is not defined (see https //psalm.dev/020)\n2pe98y r-n0u823n=r 092u3- r08u2q098ry 09nq2yr09n2yr9 y2n-93yr 298yr3 29\n\n\nERROR UndefinedConstant - php/php_bad_1.php 3 55 - Const y2n is not defined (see https //psalm.dev/020)\n2pe98y r-n0u823n=r 092u3- r08u2q098ry 09nq2yr09n2yr9 y2n-93yr 298yr3 29\n\n\nERROR ParseError - php/php_bad_1.php 3 61 - Syntax error, unexpected T_STRING on line 3 (see https //psalm.dev/173)\n2pe98y r-n0u823n=r 092u3- r08u2q098ry 09nq2yr09n2yr9 y2n-93yr 298yr3 29\n\n\nERROR UndefinedConstant - php/php_bad_1.php 3 61 - Const yr is not defined (see https //psalm.dev/020)\n2pe98y r-n0u823n=r 092u3- r08u2q098ry 09nq2yr09n2yr9 y2n-93yr 298yr3 29\n\n\nERROR UndefinedConstant - php/php_bad_1.php 3 68 - Const yr3 is not defined (see https //psalm.dev/020)\n2pe98y r-n0u823n=r 092u3- r08u2q098ry 09nq2yr09n2yr9 y2n-93yr 298yr3 29\n\n\n------------------------------\n15 errors found\n------------------------------\n\n + ... +not ok 2 - php_bad_2.php + --- + message: Scanning files...\nAnalyzing files...\n\nE\n\nERROR InvalidReturnType - php/php_bad_2.php 4 12 - The declared return type 'array' for takesAnInt is incorrect, got 'array{int, string(hello)}' (see https //psalm.dev/011)\n * @return array\n\n\nERROR InvalidReturnStatement - php/php_bad_2.php 7 12 - The inferred type 'array{int, string(hello)}' does not match the declared return type 'array' for takesAnInt (see https //psalm.dev/128)\n return [$i, "hello"];\n\n\nERROR InvalidScalarArgument - php/php_bad_2.php 11 12 - Argument 1 of takesAnInt expects int, string(some text) provided (see https //psalm.dev/012)\ntakesAnInt($data[0]);\n\n\nERROR ParseError - php/php_bad_2.php 15 1 - Syntax error, unexpected '}' on line 15 (see https //psalm.dev/173)\n} elseif ($condition) {}\n\n\n------------------------------\n4 errors found\n------------------------------\nPsalm can automatically fix 1 of these issues.\nRun Psalm again with \n--alter --issues=InvalidReturnType --dry-run\nto see what it can fix.\n------------------------------\n\n + ... +ok 3 - php_good_1.php +ok 4 - php_good_2.php diff --git a/.automation/test/python/README.md b/.automation/test/python_black/README.md similarity index 100% rename from .automation/test/python/README.md rename to .automation/test/python_black/README.md diff --git a/.automation/test/python/python_bad_1.py b/.automation/test/python_black/python_bad_1.py similarity index 100% rename from .automation/test/python/python_bad_1.py rename to .automation/test/python_black/python_bad_1.py diff --git a/.automation/test/python/python_good_1.py b/.automation/test/python_black/python_good_1.py similarity index 100% rename from .automation/test/python/python_good_1.py rename to .automation/test/python_black/python_good_1.py diff --git a/.automation/test/python/reports/expected-PYTHON.tap b/.automation/test/python_black/reports/expected-PYTHON.tap similarity index 100% rename from .automation/test/python/reports/expected-PYTHON.tap rename to .automation/test/python_black/reports/expected-PYTHON.tap diff --git a/.automation/test/python_flake8/README.md b/.automation/test/python_flake8/README.md new file mode 100644 index 00000000..aa9bb375 --- /dev/null +++ b/.automation/test/python_flake8/README.md @@ -0,0 +1,19 @@ +# Python Test Cases + +This folder holds the test cases for **Python**. + +## Additional Docs + +No Additional information is needed for this test case. + +## Good Test Cases + +The test cases denoted: `LANGUAGE_good_FILE.EXTENSION` are all valid, and should pass successfully when linted. + +- **Note:** They are linted utilizing the default linter rules. + +## Bad Test Cases + +The test cases denoted: `LANGUAGE_bad_FILE.EXTENSION` are **NOT** valid, and should trigger errors when linted. + +- **Note:** They are linted utilizing the default linter rules. diff --git a/.automation/test/python_flake8/python_bad_1.py b/.automation/test/python_flake8/python_bad_1.py new file mode 100644 index 00000000..369a7224 --- /dev/null +++ b/.automation/test/python_flake8/python_bad_1.py @@ -0,0 +1,156 @@ +import json +from os import getenv, path +from pprint import pprint +import sys + +import click # pylint: disable=import-error +from dotenv import load_dotenv # pylint: disable=import-error +import requests # pylint: disable=import-error + +env = load_dotenv() +api_url = getenv(API_URL, default='https://api.github.com/graphql' ) +github_token = getenv("GITHUB_TOKEN", +default=None) + +if github_token is None + sys.exit("GitHub Token is not set." + + "Please set the GITHUB_TOKEN env variable in your system or " + + "the .env file of your project.") + +client_id = getenv(CLIENT_ID, default='copy_labels.py') +headers = { + 'Authorization': 'bearer {github_token}'.format(github_token=github_token), + 'Accept': 'application/vnd.github.bane-preview+json' + 'Content-Type': 'application/json' +} + +def create_label(repo_id, label): + """ + Create label in the supplied repo. + + :param repo_id: Unique ID that represents the repo in GitHub + :type repo_id: str + :param label: Object with label information. + :type label: dict + :return: GitHub API request response + """ + + query_variables = { + "createLabelInput": { + "color": label["color"], + "description": label["description"], + "name": label["name"], + "repositoryId": repo_id + } + } + + with open(path.join(path.dirname(__file__), 'queries/create_label.gql'), 'r') as query_file: + query = "".join(query_file.readlines()) + + payload = {"query": query, "variables": query_variables} + response = requests.post(api_url, data=json.dumps(payload), headers=headers).json() + print('Created label {label}'.format(label=label["name"])) + + return response + +def get_labels(owner, repo): + """ + Gets a list of labels from the supplied repo. + :param owner: Repo owner GitHub login. + :type owner: str + :param repo: Repository name. + :type repo: str + :return: A tuple with the GitHub id for the repository and a list of labels defined in the repository + """ + + query_variables = { "owner": owner, "name": repo, } + + with open(path.join(path.dirname(__file__), 'queries/get_repo_data.gql'), 'r') as query_file: + query = "".join(query_file.readlines()) + + payload = {"query": query, "variables": query_variables} + response = requests.post(api_url, data=json.dumps(payload), headers=headers) + + status_code = response.status_code + result = response.json() + + if status_code >= 200 and status_code <= 300: + repo_id = result["data"]["repository"]["id"] + labels = result["data"]["repository"]["labels"]["nodes"] + + return repo_id, labels + else: + raise Exception( + '[ERROR] getting issue labels. Status Code: {status_code} - Message: {result}'.format( + status_code=status_code, result=result["message"])) + +def delete_label(label_id): + """ + Delete the specified label + :param label_id: Label's node id. + :type label_id: str + :return: GitHub API request response. + """ + + query_variables = { + "deleteLabelInput": { + "clientMutationId": client_id, + "id": label_id, + } + } + + with open(path.join(path.dirname(__file__), 'queries/delete_label.gql'), 'r') as query_file: + query = "".join(query_file.readlines()) + + payload = {"query": query, "variables": query_variables} + result = requests.post(api_url, data=json.dumps(payload), headers=headers).json() + + return result + +@click.command() +@click.option('--dry', is_flag=True) +@click.argument('source_repo') +@click.argument('target_repo') +def copy_labels(source_repo, target_repo, dry): + """ + Copy labels from the source repository to the target repository. + \f + :param source: The full name of a GitHub repo from where the labels will be copied from. Eg. github/opensourcefriday + :type source: str + :param target: The full name of a GitHub repo to where the labels will be copied. Eg. github/opensourcefriday + :type target: str + :return: + """ + source_owner, source_repo_name = source_repo.split("/") + target_owner, target_repo_name = target_repo.split("/") + + try: + print('Fetching labels for {source_repo_name} repo.'.format(source_repo_name=source_repo_name)) + _, source_repo_labels = get_labels(source_owner, source_repo_name) + print('Fetched labels for {source_repo_name}'.format(source_repo_name=source_repo_name)) + + print('Fetching labels for {target_repo_name} repo.'.format(target_repo_name=target_repo_name)) + target_repo_id, target_repo_labels = get_labels(target_owner, target_repo_name) + print('Fetched labels for {target_repo_name}'.format(target_repo_name=target_repo_name)) + + filtered_labels = list(filter(lambda x: x not in target_repo_labels, source_repo_labels)) + + if dry: + print('This is just a dry run. No labels will be copied/created.') + print('{label_count} labels would have been created.'.format(label_count=len(filtered_labels))) + pprint(filtered_labels, indent=4) + else: + print('Preparing to created {label_count} labels in {target_repo}'.format( + label_count=len(filtered_labels), target_repo=target_repo)) + + for label in filtered_labels: + create_label(target_repo_id, label) + except Exception as error: + sys.exit(error) + + print('Done') + +if __name__ == "__main__": + # Pylint doesn't know that @click.command takes care of injecting the + # function parameters. Disabling Pylint error. + copy_labels() # pylint: disable=no-value-for-parameter diff --git a/.automation/test/python_flake8/python_good_1.py b/.automation/test/python_flake8/python_good_1.py new file mode 100644 index 00000000..8106d8d8 --- /dev/null +++ b/.automation/test/python_flake8/python_good_1.py @@ -0,0 +1,195 @@ +import json +from os import getenv, path +from pprint import pprint +import sys + +import click # pylint: disable=import-error +from dotenv import load_dotenv # pylint: disable=import-error +import requests # pylint: disable=import-error + +env = load_dotenv() +api_url = getenv("API_URL", default="https://api.github.com/graphql") +github_token = getenv("GITHUB_TOKEN", default=None) + +if github_token is None: + sys.exit( + "GitHub Token is not set." + + "Please set the GITHUB_TOKEN env variable in your system or " + + "the .env file of your project." + ) + +client_id = getenv("CLIENT_ID", default="copy_labels.py") +headers = { + "Authorization": "bearer {github_token}".format(github_token=github_token), + "Accept": "application/vnd.github.bane-preview+json", + "Content-Type": "application/json", +} + + +def create_label(repo_id, label): + """ + Create label in the supplied repo. + + :param repo_id: Unique ID that represents the repo in GitHub + :type repo_id: str + :param label: Object with label information. + :type label: dict + :return: GitHub API request response + """ + + query_variables = { + "createLabelInput": { + "color": label["color"], + "description": label["description"], + "name": label["name"], + "repositoryId": repo_id, + } + } + + with open( + path.join(path.dirname(__file__), "queries/create_label.gql"), "r" + ) as query_file: + query = "".join(query_file.readlines()) + + payload = {"query": query, "variables": query_variables} + response = requests.post(api_url, data=json.dumps(payload), headers=headers).json() + print("Created label {label}".format(label=label["name"])) + + return response + + +def get_labels(owner, repo): + """ + Gets a list of labels from the supplied repo. + :param owner: Repo owner GitHub login. + :type owner: str + :param repo: Repository name. + :type repo: str + :return: A tuple with the GitHub id for the repository and a list of labels defined in the repository + """ + + query_variables = { + "owner": owner, + "name": repo, + } + + with open( + path.join(path.dirname(__file__), "queries/get_repo_data.gql"), "r" + ) as query_file: + query = "".join(query_file.readlines()) + + payload = {"query": query, "variables": query_variables} + response = requests.post(api_url, data=json.dumps(payload), headers=headers) + + status_code = response.status_code + result = response.json() + + if status_code >= 200 and status_code <= 300: + repo_id = result["data"]["repository"]["id"] + labels = result["data"]["repository"]["labels"]["nodes"] + + return repo_id, labels + else: + raise Exception( + "[ERROR] getting issue labels. Status Code: {status_code} - Message: {result}".format( + status_code=status_code, result=result["message"] + ) + ) + + +def delete_label(label_id): + """ + Delete the specified label + :param label_id: Label's node id. + :type label_id: str + :return: GitHub API request response. + """ + + query_variables = { + "deleteLabelInput": {"clientMutationId": client_id, "id": label_id} + } + + with open( + path.join(path.dirname(__file__), "queries/delete_label.gql"), "r" + ) as query_file: + query = "".join(query_file.readlines()) + + payload = {"query": query, "variables": query_variables} + result = requests.post(api_url, data=json.dumps(payload), headers=headers).json() + + return result + + +@click.command() +@click.option("--dry", is_flag=True) +@click.argument("source_repo") +@click.argument("target_repo") +def copy_labels(source_repo, target_repo, dry): + """ + Copy labels from the source repository to the target repository. + \f + :param source: The full name of a GitHub repo from where the labels will be copied from. Eg. github/opensourcefriday + :type source: str + :param target: The full name of a GitHub repo to where the labels will be copied. Eg. github/opensourcefriday + :type target: str + :return: + """ + source_owner, source_repo_name = source_repo.split("/") + target_owner, target_repo_name = target_repo.split("/") + + try: + print( + "Fetching labels for {source_repo_name} repo.".format( + source_repo_name=source_repo_name + ) + ) + _, source_repo_labels = get_labels(source_owner, source_repo_name) + print( + "Fetched labels for {source_repo_name}".format( + source_repo_name=source_repo_name + ) + ) + + print( + "Fetching labels for {target_repo_name} repo.".format( + target_repo_name=target_repo_name + ) + ) + target_repo_id, target_repo_labels = get_labels(target_owner, target_repo_name) + print( + "Fetched labels for {target_repo_name}".format( + target_repo_name=target_repo_name + ) + ) + + filtered_labels = list( + filter(lambda x: x not in target_repo_labels, source_repo_labels) + ) + + if dry: + print("This is just a dry run. No labels will be copied/created.") + print( + "{label_count} labels would have been created.".format( + label_count=len(filtered_labels) + ) + ) + pprint(filtered_labels, indent=4) + else: + print( + "Preparing to created {label_count} labels in {target_repo}".format( + label_count=len(filtered_labels), target_repo=target_repo + ) + ) + + for label in filtered_labels: + create_label(target_repo_id, label) + except Exception as error: + sys.exit(error) + + print("Done") + + +if __name__ == "__main__": + # Pylint doesn't know that @click.command takes care of injecting the + # function parameters. Disabling Pylint error. + copy_labels() # pylint: disable=no-value-for-parameter diff --git a/.automation/test/python_flake8/reports/expected-PYTHON.tap b/.automation/test/python_flake8/reports/expected-PYTHON.tap new file mode 100644 index 00000000..1751d70f --- /dev/null +++ b/.automation/test/python_flake8/reports/expected-PYTHON.tap @@ -0,0 +1,7 @@ +TAP version 13 +1..2 +not ok 1 - python_bad_1.py + --- + message: ************* Module python_bad_1\npython/python_bad_1.py 15 24 E0001 invalid syntax (, line 15) (syntax-error)\n + ... +ok 2 - python_good_1.py diff --git a/.automation/test/python_pylint/README.md b/.automation/test/python_pylint/README.md new file mode 100644 index 00000000..aa9bb375 --- /dev/null +++ b/.automation/test/python_pylint/README.md @@ -0,0 +1,19 @@ +# Python Test Cases + +This folder holds the test cases for **Python**. + +## Additional Docs + +No Additional information is needed for this test case. + +## Good Test Cases + +The test cases denoted: `LANGUAGE_good_FILE.EXTENSION` are all valid, and should pass successfully when linted. + +- **Note:** They are linted utilizing the default linter rules. + +## Bad Test Cases + +The test cases denoted: `LANGUAGE_bad_FILE.EXTENSION` are **NOT** valid, and should trigger errors when linted. + +- **Note:** They are linted utilizing the default linter rules. diff --git a/.automation/test/python_pylint/python_bad_1.py b/.automation/test/python_pylint/python_bad_1.py new file mode 100644 index 00000000..369a7224 --- /dev/null +++ b/.automation/test/python_pylint/python_bad_1.py @@ -0,0 +1,156 @@ +import json +from os import getenv, path +from pprint import pprint +import sys + +import click # pylint: disable=import-error +from dotenv import load_dotenv # pylint: disable=import-error +import requests # pylint: disable=import-error + +env = load_dotenv() +api_url = getenv(API_URL, default='https://api.github.com/graphql' ) +github_token = getenv("GITHUB_TOKEN", +default=None) + +if github_token is None + sys.exit("GitHub Token is not set." + + "Please set the GITHUB_TOKEN env variable in your system or " + + "the .env file of your project.") + +client_id = getenv(CLIENT_ID, default='copy_labels.py') +headers = { + 'Authorization': 'bearer {github_token}'.format(github_token=github_token), + 'Accept': 'application/vnd.github.bane-preview+json' + 'Content-Type': 'application/json' +} + +def create_label(repo_id, label): + """ + Create label in the supplied repo. + + :param repo_id: Unique ID that represents the repo in GitHub + :type repo_id: str + :param label: Object with label information. + :type label: dict + :return: GitHub API request response + """ + + query_variables = { + "createLabelInput": { + "color": label["color"], + "description": label["description"], + "name": label["name"], + "repositoryId": repo_id + } + } + + with open(path.join(path.dirname(__file__), 'queries/create_label.gql'), 'r') as query_file: + query = "".join(query_file.readlines()) + + payload = {"query": query, "variables": query_variables} + response = requests.post(api_url, data=json.dumps(payload), headers=headers).json() + print('Created label {label}'.format(label=label["name"])) + + return response + +def get_labels(owner, repo): + """ + Gets a list of labels from the supplied repo. + :param owner: Repo owner GitHub login. + :type owner: str + :param repo: Repository name. + :type repo: str + :return: A tuple with the GitHub id for the repository and a list of labels defined in the repository + """ + + query_variables = { "owner": owner, "name": repo, } + + with open(path.join(path.dirname(__file__), 'queries/get_repo_data.gql'), 'r') as query_file: + query = "".join(query_file.readlines()) + + payload = {"query": query, "variables": query_variables} + response = requests.post(api_url, data=json.dumps(payload), headers=headers) + + status_code = response.status_code + result = response.json() + + if status_code >= 200 and status_code <= 300: + repo_id = result["data"]["repository"]["id"] + labels = result["data"]["repository"]["labels"]["nodes"] + + return repo_id, labels + else: + raise Exception( + '[ERROR] getting issue labels. Status Code: {status_code} - Message: {result}'.format( + status_code=status_code, result=result["message"])) + +def delete_label(label_id): + """ + Delete the specified label + :param label_id: Label's node id. + :type label_id: str + :return: GitHub API request response. + """ + + query_variables = { + "deleteLabelInput": { + "clientMutationId": client_id, + "id": label_id, + } + } + + with open(path.join(path.dirname(__file__), 'queries/delete_label.gql'), 'r') as query_file: + query = "".join(query_file.readlines()) + + payload = {"query": query, "variables": query_variables} + result = requests.post(api_url, data=json.dumps(payload), headers=headers).json() + + return result + +@click.command() +@click.option('--dry', is_flag=True) +@click.argument('source_repo') +@click.argument('target_repo') +def copy_labels(source_repo, target_repo, dry): + """ + Copy labels from the source repository to the target repository. + \f + :param source: The full name of a GitHub repo from where the labels will be copied from. Eg. github/opensourcefriday + :type source: str + :param target: The full name of a GitHub repo to where the labels will be copied. Eg. github/opensourcefriday + :type target: str + :return: + """ + source_owner, source_repo_name = source_repo.split("/") + target_owner, target_repo_name = target_repo.split("/") + + try: + print('Fetching labels for {source_repo_name} repo.'.format(source_repo_name=source_repo_name)) + _, source_repo_labels = get_labels(source_owner, source_repo_name) + print('Fetched labels for {source_repo_name}'.format(source_repo_name=source_repo_name)) + + print('Fetching labels for {target_repo_name} repo.'.format(target_repo_name=target_repo_name)) + target_repo_id, target_repo_labels = get_labels(target_owner, target_repo_name) + print('Fetched labels for {target_repo_name}'.format(target_repo_name=target_repo_name)) + + filtered_labels = list(filter(lambda x: x not in target_repo_labels, source_repo_labels)) + + if dry: + print('This is just a dry run. No labels will be copied/created.') + print('{label_count} labels would have been created.'.format(label_count=len(filtered_labels))) + pprint(filtered_labels, indent=4) + else: + print('Preparing to created {label_count} labels in {target_repo}'.format( + label_count=len(filtered_labels), target_repo=target_repo)) + + for label in filtered_labels: + create_label(target_repo_id, label) + except Exception as error: + sys.exit(error) + + print('Done') + +if __name__ == "__main__": + # Pylint doesn't know that @click.command takes care of injecting the + # function parameters. Disabling Pylint error. + copy_labels() # pylint: disable=no-value-for-parameter diff --git a/.automation/test/python_pylint/python_good_1.py b/.automation/test/python_pylint/python_good_1.py new file mode 100644 index 00000000..8106d8d8 --- /dev/null +++ b/.automation/test/python_pylint/python_good_1.py @@ -0,0 +1,195 @@ +import json +from os import getenv, path +from pprint import pprint +import sys + +import click # pylint: disable=import-error +from dotenv import load_dotenv # pylint: disable=import-error +import requests # pylint: disable=import-error + +env = load_dotenv() +api_url = getenv("API_URL", default="https://api.github.com/graphql") +github_token = getenv("GITHUB_TOKEN", default=None) + +if github_token is None: + sys.exit( + "GitHub Token is not set." + + "Please set the GITHUB_TOKEN env variable in your system or " + + "the .env file of your project." + ) + +client_id = getenv("CLIENT_ID", default="copy_labels.py") +headers = { + "Authorization": "bearer {github_token}".format(github_token=github_token), + "Accept": "application/vnd.github.bane-preview+json", + "Content-Type": "application/json", +} + + +def create_label(repo_id, label): + """ + Create label in the supplied repo. + + :param repo_id: Unique ID that represents the repo in GitHub + :type repo_id: str + :param label: Object with label information. + :type label: dict + :return: GitHub API request response + """ + + query_variables = { + "createLabelInput": { + "color": label["color"], + "description": label["description"], + "name": label["name"], + "repositoryId": repo_id, + } + } + + with open( + path.join(path.dirname(__file__), "queries/create_label.gql"), "r" + ) as query_file: + query = "".join(query_file.readlines()) + + payload = {"query": query, "variables": query_variables} + response = requests.post(api_url, data=json.dumps(payload), headers=headers).json() + print("Created label {label}".format(label=label["name"])) + + return response + + +def get_labels(owner, repo): + """ + Gets a list of labels from the supplied repo. + :param owner: Repo owner GitHub login. + :type owner: str + :param repo: Repository name. + :type repo: str + :return: A tuple with the GitHub id for the repository and a list of labels defined in the repository + """ + + query_variables = { + "owner": owner, + "name": repo, + } + + with open( + path.join(path.dirname(__file__), "queries/get_repo_data.gql"), "r" + ) as query_file: + query = "".join(query_file.readlines()) + + payload = {"query": query, "variables": query_variables} + response = requests.post(api_url, data=json.dumps(payload), headers=headers) + + status_code = response.status_code + result = response.json() + + if status_code >= 200 and status_code <= 300: + repo_id = result["data"]["repository"]["id"] + labels = result["data"]["repository"]["labels"]["nodes"] + + return repo_id, labels + else: + raise Exception( + "[ERROR] getting issue labels. Status Code: {status_code} - Message: {result}".format( + status_code=status_code, result=result["message"] + ) + ) + + +def delete_label(label_id): + """ + Delete the specified label + :param label_id: Label's node id. + :type label_id: str + :return: GitHub API request response. + """ + + query_variables = { + "deleteLabelInput": {"clientMutationId": client_id, "id": label_id} + } + + with open( + path.join(path.dirname(__file__), "queries/delete_label.gql"), "r" + ) as query_file: + query = "".join(query_file.readlines()) + + payload = {"query": query, "variables": query_variables} + result = requests.post(api_url, data=json.dumps(payload), headers=headers).json() + + return result + + +@click.command() +@click.option("--dry", is_flag=True) +@click.argument("source_repo") +@click.argument("target_repo") +def copy_labels(source_repo, target_repo, dry): + """ + Copy labels from the source repository to the target repository. + \f + :param source: The full name of a GitHub repo from where the labels will be copied from. Eg. github/opensourcefriday + :type source: str + :param target: The full name of a GitHub repo to where the labels will be copied. Eg. github/opensourcefriday + :type target: str + :return: + """ + source_owner, source_repo_name = source_repo.split("/") + target_owner, target_repo_name = target_repo.split("/") + + try: + print( + "Fetching labels for {source_repo_name} repo.".format( + source_repo_name=source_repo_name + ) + ) + _, source_repo_labels = get_labels(source_owner, source_repo_name) + print( + "Fetched labels for {source_repo_name}".format( + source_repo_name=source_repo_name + ) + ) + + print( + "Fetching labels for {target_repo_name} repo.".format( + target_repo_name=target_repo_name + ) + ) + target_repo_id, target_repo_labels = get_labels(target_owner, target_repo_name) + print( + "Fetched labels for {target_repo_name}".format( + target_repo_name=target_repo_name + ) + ) + + filtered_labels = list( + filter(lambda x: x not in target_repo_labels, source_repo_labels) + ) + + if dry: + print("This is just a dry run. No labels will be copied/created.") + print( + "{label_count} labels would have been created.".format( + label_count=len(filtered_labels) + ) + ) + pprint(filtered_labels, indent=4) + else: + print( + "Preparing to created {label_count} labels in {target_repo}".format( + label_count=len(filtered_labels), target_repo=target_repo + ) + ) + + for label in filtered_labels: + create_label(target_repo_id, label) + except Exception as error: + sys.exit(error) + + print("Done") + + +if __name__ == "__main__": + # Pylint doesn't know that @click.command takes care of injecting the + # function parameters. Disabling Pylint error. + copy_labels() # pylint: disable=no-value-for-parameter diff --git a/.automation/test/python_pylint/reports/expected-PYTHON.tap b/.automation/test/python_pylint/reports/expected-PYTHON.tap new file mode 100644 index 00000000..1751d70f --- /dev/null +++ b/.automation/test/python_pylint/reports/expected-PYTHON.tap @@ -0,0 +1,7 @@ +TAP version 13 +1..2 +not ok 1 - python_bad_1.py + --- + message: ************* Module python_bad_1\npython/python_bad_1.py 15 24 E0001 invalid syntax (, line 15) (syntax-error)\n + ... +ok 2 - python_good_1.py diff --git a/.automation/test/snakemake/README.md b/.automation/test/snakemake_lint/README.md similarity index 100% rename from .automation/test/snakemake/README.md rename to .automation/test/snakemake_lint/README.md diff --git a/.automation/test/snakemake/snakemake_bad_1.smk b/.automation/test/snakemake_lint/snakemake_bad_1.smk similarity index 100% rename from .automation/test/snakemake/snakemake_bad_1.smk rename to .automation/test/snakemake_lint/snakemake_bad_1.smk diff --git a/.automation/test/snakemake/snakemake_good_1.smk b/.automation/test/snakemake_lint/snakemake_good_1.smk similarity index 100% rename from .automation/test/snakemake/snakemake_good_1.smk rename to .automation/test/snakemake_lint/snakemake_good_1.smk diff --git a/.automation/test/snakemake_snakefmt/README.md b/.automation/test/snakemake_snakefmt/README.md new file mode 100644 index 00000000..e25faf23 --- /dev/null +++ b/.automation/test/snakemake_snakefmt/README.md @@ -0,0 +1,19 @@ +# Snakemake test cases + +This folder holds the test cases for **Snakemake**. + +## Additional Docs + +No Additional information is needed for this test case. + +## Good Test Cases + +The test cases denoted: `LANGUAGE_good_FILE.EXTENSION` are all valid, and should pass successfully when linted. + +- **Note:** They are linted utilizing the default linter rules. + +## Bad Test Cases + +The test cases denoted: `LANGUAGE_bad_FILE.EXTENSION` are **NOT** valid, and should trigger errors when linted. + +- **Note:** They are linted utilizing the default linter rules. diff --git a/.automation/test/snakemake_snakefmt/snakemake_bad_1.smk b/.automation/test/snakemake_snakefmt/snakemake_bad_1.smk new file mode 100644 index 00000000..6a8a9592 --- /dev/null +++ b/.automation/test/snakemake_snakefmt/snakemake_bad_1.smk @@ -0,0 +1,11 @@ +rule all: + input: + file1='result.txt', + +rule simulation: + output: + file1="result.txt" + shell: + """ + touch {output} + """ diff --git a/.automation/test/snakemake_snakefmt/snakemake_good_1.smk b/.automation/test/snakemake_snakefmt/snakemake_good_1.smk new file mode 100644 index 00000000..e4b362eb --- /dev/null +++ b/.automation/test/snakemake_snakefmt/snakemake_good_1.smk @@ -0,0 +1,16 @@ +rule all: + input: + file1="result.txt", + + +rule simulation: + output: + file1="result.txt", + log: + "logs/simulation.log", + conda: + "envs/simulation.yml" + shell: + """ + touch {output} + """ diff --git a/.automation/test/typescript/README.md b/.automation/test/typescript_es/README.md similarity index 100% rename from .automation/test/typescript/README.md rename to .automation/test/typescript_es/README.md diff --git a/.automation/test/typescript/reports/expected-TYPESCRIPT_ES.tap b/.automation/test/typescript_es/reports/expected-TYPESCRIPT_ES.tap similarity index 100% rename from .automation/test/typescript/reports/expected-TYPESCRIPT_ES.tap rename to .automation/test/typescript_es/reports/expected-TYPESCRIPT_ES.tap diff --git a/.automation/test/typescript/reports/expected-TYPESCRIPT_STANDARD.tap b/.automation/test/typescript_es/reports/expected-TYPESCRIPT_STANDARD.tap similarity index 100% rename from .automation/test/typescript/reports/expected-TYPESCRIPT_STANDARD.tap rename to .automation/test/typescript_es/reports/expected-TYPESCRIPT_STANDARD.tap diff --git a/.automation/test/typescript/typescript_bad_1.ts b/.automation/test/typescript_es/typescript_bad_1.ts similarity index 100% rename from .automation/test/typescript/typescript_bad_1.ts rename to .automation/test/typescript_es/typescript_bad_1.ts diff --git a/.automation/test/typescript/typescript_good_1.ts b/.automation/test/typescript_es/typescript_good_1.ts similarity index 100% rename from .automation/test/typescript/typescript_good_1.ts rename to .automation/test/typescript_es/typescript_good_1.ts diff --git a/.automation/test/typescript_standard/README.md b/.automation/test/typescript_standard/README.md new file mode 100644 index 00000000..19cd7a3d --- /dev/null +++ b/.automation/test/typescript_standard/README.md @@ -0,0 +1,19 @@ +# Typescript Test Cases + +This folder holds the test cases for **Typescript**. + +## Additional Docs + +No Additional information is needed for this test case. + +## Good Test Cases + +The test cases denoted: `LANGUAGE_good_FILE.EXTENSION` are all valid, and should pass successfully when linted. + +- **Note:** They are linted utilizing the default linter rules. + +## Bad Test Cases + +The test cases denoted: `LANGUAGE_bad_FILE.EXTENSION` are **NOT** valid, and should trigger errors when linted. + +- **Note:** They are linted utilizing the default linter rules. diff --git a/.automation/test/typescript_standard/reports/expected-TYPESCRIPT_ES.tap b/.automation/test/typescript_standard/reports/expected-TYPESCRIPT_ES.tap new file mode 100644 index 00000000..c08faa21 --- /dev/null +++ b/.automation/test/typescript_standard/reports/expected-TYPESCRIPT_ES.tap @@ -0,0 +1,7 @@ +TAP version 13 +1..2 +not ok 1 - typescript_bad_1.ts + --- + message: \n/tmp/lint/.automation/test/typescript/typescript_bad_1.ts\n 5 39 error Parsing error Unterminated regular expression literal\n\n✖ 1 problem (1 error, 0 warnings)\n + ... +ok 2 - typescript_good_1.ts diff --git a/.automation/test/typescript_standard/reports/expected-TYPESCRIPT_STANDARD.tap b/.automation/test/typescript_standard/reports/expected-TYPESCRIPT_STANDARD.tap new file mode 100644 index 00000000..1271fc8f --- /dev/null +++ b/.automation/test/typescript_standard/reports/expected-TYPESCRIPT_STANDARD.tap @@ -0,0 +1,7 @@ +TAP version 13 +1..2 +not ok 1 - typescript_bad_1.ts + --- + message: standard Use JavaScript Standard Style (https //standardjs.com)\n /tmp/lint/.automation/test/typescript/typescript_bad_1.ts 5 39 Parsing error Unterminated regular expression literal.\n + ... +ok 2 - typescript_good_1.ts diff --git a/.automation/test/typescript_standard/typescript_bad_1.ts b/.automation/test/typescript_standard/typescript_bad_1.ts new file mode 100644 index 00000000..1b484c9f --- /dev/null +++ b/.automation/test/typescript_standard/typescript_bad_1.ts @@ -0,0 +1,8 @@ +const spiderman = (person: String) => { + return 'Hello, ' + person; +} + +var handler = createHandler( { path : /webhook, secret : (process.env.SECRET) }) + +let user = 1; +console.log(spiderman(user)); diff --git a/.automation/test/typescript_standard/typescript_good_1.ts b/.automation/test/typescript_standard/typescript_good_1.ts new file mode 100644 index 00000000..68ef03e0 --- /dev/null +++ b/.automation/test/typescript_standard/typescript_good_1.ts @@ -0,0 +1,6 @@ +const spiderman = (person) => { + return 'Hello, ' + person +} + +const user = 'Peter Parker' +console.log(spiderman(user)) diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 00000000..67fe2dd6 --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +!.automation diff --git a/.github/workflows/deploy-DEV.yml b/.github/workflows/deploy-DEV.yml index 9c2a4512..706b4122 100644 --- a/.github/workflows/deploy-DEV.yml +++ b/.github/workflows/deploy-DEV.yml @@ -82,4 +82,4 @@ jobs: ############################################ - name: Run against all code base shell: bash - run: docker run -e RUN_LOCAL=true -e OUTPUT_DETAILS=detailed -v ACTIONS_RUNNER_DEBUG=true -v ${GITHUB_WORKSPACE}:/tmp/lint github/super-linter:${GITHUB_SHA} + run: docker run -e RUN_LOCAL=true -e OUTPUT_DETAILS=detailed -e ACTIONS_RUNNER_DEBUG=true -v ${GITHUB_WORKSPACE}:/tmp/lint github/super-linter:${GITHUB_SHA} diff --git a/.github/workflows/stack-linter.yml b/.github/workflows/stack-linter.yml index ae41b6ab..238c5052 100644 --- a/.github/workflows/stack-linter.yml +++ b/.github/workflows/stack-linter.yml @@ -47,5 +47,6 @@ jobs: - name: Lint Code Base uses: docker://ghcr.io/github/super-linter:latest env: + ACTIONS_RUNNER_DEBUG: true VALIDATE_ALL_CODEBASE: false GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/lib/buildFileList.sh b/lib/buildFileList.sh index afcb72f4..9e83ece6 100755 --- a/lib/buildFileList.sh +++ b/lib/buildFileList.sh @@ -18,7 +18,10 @@ function BuildFileList() { VALIDATE_ALL_CODEBASE="${1}" debug "Validate all code base: ${VALIDATE_ALL_CODEBASE}..." - if [ "${VALIDATE_ALL_CODEBASE}" == "false" ]; then + TEST_CASE_RUN="${2}" + debug "TEST_CASE_RUN: ${TEST_CASE_RUN}..." + + if [ "${VALIDATE_ALL_CODEBASE}" == "false" ] && [ "${TEST_CASE_RUN}" != "true" ]; then # Need to build a list of all files changed # This can be pulled from the GITHUB_EVENT_PATH payload @@ -61,12 +64,17 @@ function BuildFileList() { ################################################# mapfile -t RAW_FILE_ARRAY < <(git -C "${GITHUB_WORKSPACE}" diff --name-only "${DEFAULT_BRANCH}...${GITHUB_SHA}" --diff-filter=d 2>&1) else + WORKSPACE_PATH="${GITHUB_WORKSPACE}" + if [ "${TEST_CASE_RUN}" != "true" ]; then + WORKSPACE_PATH="${GITHUB_WORKSPACE}/${TEST_CASE_FOLDER}" + fi + ################ # print header # ################ debug "----------------------------------------------" - debug "Populating the file list with all the files in the ${GITHUB_WORKSPACE} workspace" - mapfile -t RAW_FILE_ARRAY < <(find "${GITHUB_WORKSPACE}" \ + debug "Populating the file list with all the files in the ${WORKSPACE_PATH} workspace" + mapfile -t RAW_FILE_ARRAY < <(find "${WORKSPACE_PATH}" \ -path "*/node_modules" -prune -o \ -path "*/.git" -prune -o \ -path "*/.venv" -prune -o \ @@ -98,7 +106,7 @@ function BuildFileList() { ############################### warn "No files were found in the GITHUB_WORKSPACE:[${GITHUB_WORKSPACE}] to lint!" fi - + ################################################ # Iterate through the array of all files found # ################################################ @@ -116,6 +124,19 @@ function BuildFileList() { ############## debug "File:[${FILE}], File_type:[${FILE_TYPE}], Base_file:[${BASE_FILE}]" + ######################################################## + # Don't include test cases if not running in test mode # + ######################################################## + if [[ ${FILE} == *"${TEST_CASE_FOLDER}"* ]] && [ "${TEST_CASE_RUN}" != "true" ]; then + debug "TEST_CASE_RUN (${TEST_CASE_RUN}) is not true. Skipping ${FILE}..." + continue + ################################################## + # Include test cases if not running in test mode # + ################################################## + elif [[ ${FILE} != *"${TEST_CASE_FOLDER}"* ]] && [ "${TEST_CASE_RUN}" == "true" ]; then + debug "TEST_CASE_RUN (${TEST_CASE_RUN}) is true. Skipping ${FILE}..." + fi + # Editorconfig-checker should check every file FILE_ARRAY_EDITORCONFIG+=("${FILE}") @@ -453,7 +474,7 @@ function BuildFileList() { ############################ # Get the Terragrunt files # ############################ - elif [ "${FILE_TYPE}" == "hcl" ]; then + elif [ "${FILE_TYPE}" == "hcl" ] && [[ ${FILE} != *".tflint.hcl"* ]]; then ################################ # Append the file to the array # ################################ diff --git a/lib/linter.sh b/lib/linter.sh index b0044c04..6cf953e5 100755 --- a/lib/linter.sh +++ b/lib/linter.sh @@ -143,6 +143,7 @@ LANGUAGE_ARRAY=('ANSIBLE' 'ARM' 'BASH' 'BASH_EXEC' 'CLOUDFORMATION' 'CLOJURE' 'C # Linter command names array # ############################## declare -A LINTER_NAMES_ARRAY +LINTER_NAMES_ARRAY['ANSIBLE']="ansible-lint" LINTER_NAMES_ARRAY['ARM']="arm-ttk" LINTER_NAMES_ARRAY['BASH']="shellcheck" LINTER_NAMES_ARRAY['BASH_EXEC']="bash-exec" @@ -180,7 +181,7 @@ LINTER_NAMES_ARRAY['PROTOBUF']="protolint" LINTER_NAMES_ARRAY['PYTHON_BLACK']="black" LINTER_NAMES_ARRAY['PYTHON_PYLINT']="pylint" LINTER_NAMES_ARRAY['PYTHON_FLAKE8']="flake8" -LINTER_NAMES_ARRAY['R']="lintr" +LINTER_NAMES_ARRAY['R']="R" LINTER_NAMES_ARRAY['RAKU']="raku" LINTER_NAMES_ARRAY['RUBY']="rubocop" LINTER_NAMES_ARRAY['SHELL_SHFMT']="shfmt" @@ -259,6 +260,25 @@ for LANGUAGE in "${LANGUAGE_ARRAY[@]}"; do eval "${FILE_ARRAY_VARIABLE_NAME}=()" done +##################################### +# Validate we have linter installed # +##################################### +for LANGUAGE in "${LANGUAGE_ARRAY[@]}"; do + LINTER_NAME="${LINTER_NAMES_ARRAY["${LANGUAGE}"]}" + debug "Checking if linter with name ${LINTER_NAME} for the ${LANGUAGE} language is available..." + + if ! command -v "${LINTER_NAME}" 1&>/dev/null 2>&1; then + # Failed + error "Failed to find [${LINTER_NAME}] in system!" + fatal "[${VALIDATE_INSTALL_CMD}]" + else + # Success + debug "Successfully found binary for ${F[W]}[${LINTER_NAME}]${F[B]} in system location: ${F[W]}[${VALIDATE_INSTALL_CMD}]" + fi +done + + + ################################################################################ ########################## FUNCTIONS BELOW ##################################### ################################################################################ @@ -504,6 +524,7 @@ DetectOpenAPIFile() { # Pull in vars # ################ FILE="${1}" + debug "Checking if ${FILE} is an OpenAPI file..." ############################### # Check the file for keywords # @@ -537,6 +558,7 @@ DetectTektonFile() { # Pull in vars # ################ FILE="${1}" + debug "Checking if ${FILE} is a Tekton file..." ############################### # Check the file for keywords # @@ -570,6 +592,7 @@ DetectARMFile() { # Pull in vars # ################ FILE="${1}" # Name of the file/path we are validating + debug "Checking if ${FILE} is an ARM file..." ############################### # Check the file for keywords # @@ -603,6 +626,7 @@ DetectCloudFormationFile() { # Pull in Vars # ################ FILE="${1}" # File that we need to validate + debug "Checking if ${FILE} is a Cloud Formation file..." # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-formats.html # AWSTemplateFormatVersion is optional @@ -651,6 +675,7 @@ DetectAWSStatesFIle() { # Pull in Vars # ################ FILE="${1}" # File that we need to validate + debug "Checking if ${FILE} is a AWS states descriptor..." # https://states-language.net/spec.html#example ############################### @@ -1189,27 +1214,10 @@ debug "---------------------------------------------" ################################## GetLinterVersions -########################################### -# Check to see if this is a test case run # -########################################### -if [[ ${TEST_CASE_RUN} != "false" ]]; then - - ############################################# - # Set the multi status to off for test runs # - ############################################# - MULTI_STATUS='false' - - ########################### - # Run only the test cases # - ########################### - # Code will exit from inside this loop - RunTestCases -fi - ########################################### # Build the list of files for each linter # ########################################### -BuildFileList "${VALIDATE_ALL_CODEBASE}" +BuildFileList "${VALIDATE_ALL_CODEBASE}" "${TEST_CASE_RUN}" ############### # Run linters # @@ -1278,8 +1286,8 @@ for LANGUAGE in "${LANGUAGE_ARRAY[@]}"; do LANGUAGE_FILE_ARRAY="${FILE_ARRAY_VARIABLE_NAME}"[@] debug "${FILE_ARRAY_VARIABLE_NAME} file array contents: ${!LANGUAGE_FILE_ARRAY}" - debug "Invoking ${LINTER_NAME} linter..." - LintCodebase "${LANGUAGE}" "${LINTER_NAME}" "${LINTER_COMMAND}" "${FILTER_REGEX_INCLUDE}" "${FILTER_REGEX_EXCLUDE}" "${!LANGUAGE_FILE_ARRAY}" + debug "Invoking ${LINTER_NAME} linter. TEST_CASE_RUN: ${TEST_CASE_RUN}" + LintCodebase "${LANGUAGE}" "${LINTER_NAME}" "${LINTER_COMMAND}" "${FILTER_REGEX_INCLUDE}" "${FILTER_REGEX_EXCLUDE}" "${TEST_CASE_RUN}" "${!LANGUAGE_FILE_ARRAY}" fi fi done diff --git a/lib/worker.sh b/lib/worker.sh index 37f14fd2..f6693b2b 100755 --- a/lib/worker.sh +++ b/lib/worker.sh @@ -18,48 +18,26 @@ function LintCodebase() { LINTER_COMMAND="${1}" && shift # Pull the variable and remove from array path (Example: jsonlint -c ConfigFile /path/to/file) FILTER_REGEX_INCLUDE="${1}" && shift # Pull the variable and remove from array path (Example: */src/*,*/test/*) FILTER_REGEX_EXCLUDE="${1}" && shift # Pull the variable and remove from array path (Example: */examples/*,*/test/*.test) + TEST_CASE_RUN="${1}" && shift FILE_ARRAY=("$@") # Array of files to validate (Example: ${FILE_ARRAY_JSON}) - ###################### - # Create Print Array # - ###################### - PRINT_ARRAY=() + debug "Running LintCodebase. FILE_TYPE: ${FILE_TYPE}. Linter name: ${LINTER_NAME}, linter command: ${LINTER_COMMAND}, TEST_CASE_RUN: ${TEST_CASE_RUN}, FILTER_REGEX_INCLUDE: ${FILTER_REGEX_INCLUDE}, FILTER_REGEX_EXCLUDE: ${FILTER_REGEX_EXCLUDE} files to lint: ${FILE_ARRAY[*]}" ################ # print header # ################ - PRINT_ARRAY+=("") - PRINT_ARRAY+=("----------------------------------------------") - PRINT_ARRAY+=("----------------------------------------------") - PRINT_ARRAY+=("Linting [${FILE_TYPE}] files...") - PRINT_ARRAY+=("----------------------------------------------") - PRINT_ARRAY+=("----------------------------------------------") + info "" + info "----------------------------------------------" + info "----------------------------------------------" - ##################################### - # Validate we have linter installed # - ##################################### - # Edgecase for Lintr as it is a Package for R - if [[ ${LINTER_NAME} == *"lintr"* ]]; then - VALIDATE_INSTALL_CMD=$(command -v "R" 2>&1) + if [ "${TEST_CASE_RUN}" = "true" ]; then + info "Testing Codebase [${FILE_TYPE}] files..." else - VALIDATE_INSTALL_CMD=$(command -v "${LINTER_NAME}" 2>&1) + info "Linting [${FILE_TYPE}] files..." fi - ####################### - # Load the error code # - ####################### - ERROR_CODE=$? - ############################## - # Check the shell for errors # - ############################## - if [ ${ERROR_CODE} -ne 0 ]; then - # Failed - error "Failed to find [${LINTER_NAME}] in system!" - fatal "[${VALIDATE_INSTALL_CMD}]" - else - # Success - debug "Successfully found binary for ${F[W]}[${LINTER_NAME}]${F[B]} in system location: ${F[W]}[${VALIDATE_INSTALL_CMD}]" - fi + info "----------------------------------------------" + info "----------------------------------------------" ########################## # Initialize empty Array # @@ -82,6 +60,8 @@ function LintCodebase() { LIST_FILES=("${FILE_ARRAY[@]}") # Copy the array into list fi + debug "SKIP_FLAG: ${SKIP_FLAG}, list of files to lint: ${LIST_FILES[*]}" + ################################################# # Filter files if FILTER_REGEX_INCLUDE is set # ################################################# @@ -89,6 +69,7 @@ function LintCodebase() { for index in "${!LIST_FILES[@]}"; do [[ ! (${LIST_FILES[$index]} =~ $FILTER_REGEX_INCLUDE) ]] && unset -v 'LIST_FILES[$index]' done + debug "List of files to lint after FILTER_REGEX_INCLUDE: ${LIST_FILES[*]}" fi ################################################# @@ -98,22 +79,13 @@ function LintCodebase() { for index in "${!LIST_FILES[@]}"; do [[ ${LIST_FILES[$index]} =~ $FILTER_REGEX_EXCLUDE ]] && unset -v 'LIST_FILES[$index]' done + debug "List of files to lint after FILTER_REGEX_EXCLUDE: ${LIST_FILES[*]}" fi ############################### # Check if any data was found # ############################### if [ ${SKIP_FLAG} -eq 0 ]; then - ###################### - # Print Header array # - ###################### - for LINE in "${PRINT_ARRAY[@]}"; do - ######################### - # Print the header info # - ######################### - info "${LINE}" - done - ######################################## # Prepare context if TAP format output # ######################################## @@ -124,6 +96,12 @@ function LintCodebase() { REPORT_OUTPUT_FILE="${REPORT_OUTPUT_FOLDER}/super-linter-${FILE_TYPE}.${OUTPUT_FORMAT}" fi + WORKSPACE_PATH="${GITHUB_WORKSPACE}" + if [ "${TEST_CASE_RUN}" != "true" ]; then + WORKSPACE_PATH="${GITHUB_WORKSPACE}/${TEST_CASE_FOLDER}" + fi + debug "Workspace path: ${WORKSPACE_PATH}" + ################## # Lint the files # ################## @@ -134,35 +112,42 @@ function LintCodebase() { FILE_NAME=$(basename "${FILE}" 2>&1) DIR_NAME=$(dirname "${FILE}" 2>&1) - ###################################################### - # Make sure we don't lint node modules or test cases # - ###################################################### - if [[ ${FILE} == *"node_modules"* ]]; then - # This is a node modules file - continue - elif [[ ${FILE} == *"${TEST_CASE_FOLDER}"* ]]; then - # This is the test cases, we should always skip - continue - elif [[ ${FILE} == *".git" ]] || [[ ${FILE} == *".git/"* ]]; then - # This is likely the .git folder and shouldn't be parsed - continue - elif [[ ${FILE} == *".venv"* ]]; then - # This is likely the python virtual environment folder and shouldn't be parsed - continue - elif [[ ${FILE} == *".rbenv"* ]]; then - # This is likely the ruby environment folder and shouldn't be parsed - continue - elif [[ ${FILE_TYPE} == "BASH" ]] && ! IsValidShellScript "${FILE}"; then - # not a valid script and we need to skip - continue - elif [[ ${FILE_TYPE} == "BASH_EXEC" ]] && ! IsValidShellScript "${FILE}"; then - # not a valid script and we need to skip - continue - elif [[ ${FILE_TYPE} == "SHELL_SHFMT" ]] && ! IsValidShellScript "${FILE}"; then - # not a valid script and we need to skip - continue - elif [[ ${FILE_TYPE} == "TERRAGRUNT" ]] && [[ ${FILE} == *".tflint.hcl"* ]]; then - # This is likely a tflint configuration file and should not be linted by Terragrunt + ############################ + # Get the file pass status # + ############################ + # Example: markdown_good_1.md -> good + FILE_STATUS=$(echo "${FILE_NAME}" | cut -f2 -d'_') + + ######################################################### + # If not found, assume it should be linted successfully # + ######################################################### + if [ -z "${FILE_STATUS}" ] || { [ "${FILE_STATUS}" != "good" ] && [ "${FILE_STATUS}" != "bad" ]; }; then + FILE_STATUS="good" + fi + + ####################################### + # Check if docker and get folder name # + ####################################### + if [[ ${FILE_TYPE} == *"DOCKER"* ]]; then + if [[ ${FILE} == *"good"* ]]; then + ############# + # Good file # + ############# + FILE_STATUS='good' + else + ############ + # Bad file # + ############ + FILE_STATUS='bad' + fi + fi + + INDIVIDUAL_TEST_FOLDER="${FILE_TYPE,,}" # Folder for specific tests. By convention, it's the lowercased FILE_TYPE + + debug "File: ${FILE}, FILE_NAME: ${FILE_NAME}, DIR_NAME:${DIR_NAME}, FILE_STATUS: ${FILE_STATUS}, INDIVIDUAL_TEST_FOLDER: ${INDIVIDUAL_TEST_FOLDER}" + + if [[ ${FILE} != *"${TEST_CASE_FOLDER}/${INDIVIDUAL_TEST_FOLDER}/"* ]] && [ "${TEST_CASE_RUN}" == "true" ]; then + debug "Skipping ${FILE} because it's not in the test case directory for ${FILE_TYPE}..." continue fi @@ -187,18 +172,37 @@ function LintCodebase() { #################### LINT_CMD='' + ##################### + # Check for ansible # + ##################### + if [[ ${FILE_TYPE} == "ANSIBLE" ]]; then + ######################################### + # Make sure we don't lint certain files # + ######################################### + if [[ ${FILE} == *"vault.yml"* ]] || [[ ${FILE} == *"galaxy.yml"* ]]; then + # This is a file we don't look at + continue + fi + + ################################ + # Lint the file with the rules # + ################################ + LINT_CMD=$( + cd "${WORKSPACE_PATH}/ansible" || exit + ${LINTER_COMMAND} "${FILE}" 2>&1 + ) #################################### # Corner case for pwsh subshell # # - PowerShell (PSScriptAnalyzer) # # - ARM (arm-ttk) # #################################### - if [[ ${FILE_TYPE} == "POWERSHELL" ]] || [[ ${FILE_TYPE} == "ARM" ]]; then + elif [[ ${FILE_TYPE} == "POWERSHELL" ]] || [[ ${FILE_TYPE} == "ARM" ]]; then ################################ # Lint the file with the rules # ################################ # Need to run PowerShell commands using pwsh -c, also exit with exit code from inner subshell LINT_CMD=$( - cd "${GITHUB_WORKSPACE}" || exit + cd "${WORKSPACE_PATH}" || exit pwsh -NoProfile -NoLogo -Command "${LINTER_COMMAND} ${FILE}; if (\${Error}.Count) { exit 1 }" exit $? 2>&1 ) @@ -210,7 +214,7 @@ function LintCodebase() { # Lint the file with the updated path # ####################################### LINT_CMD=$( - cd "${GITHUB_WORKSPACE}" || exit + cd "${WORKSPACE_PATH}" || exit ${LINTER_COMMAND} --path "${DIR_NAME}" --files "$FILE_NAME" 2>&1 ) ############################################################################### @@ -221,7 +225,7 @@ function LintCodebase() { # Lint the file with the updated path # ####################################### if [ ! -f "${DIR_NAME}/.lintr" ]; then - r_dir="${GITHUB_WORKSPACE}" + r_dir="${WORKSPACE_PATH}" else r_dir="${DIR_NAME}" fi @@ -229,7 +233,6 @@ function LintCodebase() { cd "$r_dir" || exit R --slave -e "errors <- lintr::lint('$FILE');print(errors);quit(save = 'no', status = if (length(errors) > 0) 1 else 0)" 2>&1 ) - #LINTER_COMMAND="lintr::lint('${FILE}')" ######################################################### # Corner case for C# as it writes to tty and not stdout # ######################################################### @@ -244,7 +247,7 @@ function LintCodebase() { # Lint the file with the rules # ################################ LINT_CMD=$( - cd "${GITHUB_WORKSPACE}" || exit + cd "${WORKSPACE_PATH}" || exit ${LINTER_COMMAND} "${FILE}" 2>&1 ) fi @@ -253,48 +256,83 @@ function LintCodebase() { ####################### ERROR_CODE=$? - ############################## - # Check the shell for errors # - ############################## - if [ ${ERROR_CODE} -ne 0 ]; then - debug "Found errors. Error code: ${ERROR_CODE}, File type: ${FILE_TYPE}, Error on missing exec bit: ${ERROR_ON_MISSING_EXEC_BIT}" - if [[ ${FILE_TYPE} == "BASH_EXEC" ]] && [[ "${ERROR_ON_MISSING_EXEC_BIT}" == "false" ]]; then - ######## - # WARN # - ######## - warn "Warnings found in [${LINTER_NAME}] linter!" - warn "${LINT_CMD}" + ######################################## + # Check for if it was supposed to pass # + ######################################## + if [[ ${FILE_STATUS} == "good" ]]; then + ############################## + # Check the shell for errors # + ############################## + if [ ${ERROR_CODE} -ne 0 ]; then + debug "Found errors. Error code: ${ERROR_CODE}, File type: ${FILE_TYPE}, Error on missing exec bit: ${ERROR_ON_MISSING_EXEC_BIT}" + if [[ ${FILE_TYPE} == "BASH_EXEC" ]] && [[ "${ERROR_ON_MISSING_EXEC_BIT}" == "false" ]]; then + ######## + # WARN # + ######## + warn "Warnings found in [${LINTER_NAME}] linter!" + warn "${LINT_CMD}" + else + ######### + # Error # + ######### + error "Found errors in [${LINTER_NAME}] linter!" + error "Error code: ${ERROR_CODE}. Command run:${NC}[\$${LINT_CMD}]" + # Increment the error count + (("ERRORS_FOUND_${FILE_TYPE}++")) + fi + + ####################################################### + # Store the linting as a temporary file in TAP format # + ####################################################### + if IsTAP; then + NotOkTap "${INDEX}" "${FILE}" "${TMPFILE}" + AddDetailedMessageIfEnabled "${LINT_CMD}" "${TMPFILE}" + fi else + ########### + # Success # + ########### + info " - File:${F[W]}[${FILE_NAME}]${F[B]} was linted with ${F[W]}[${LINTER_NAME}]${F[B]} successfully" + + ####################################################### + # Store the linting as a temporary file in TAP format # + ####################################################### + if IsTAP; then + OkTap "${INDEX}" "${FILE}" "${TMPFILE}" + fi + fi + else + ####################################### + # File status = bad, this should fail # + ####################################### + ############################## + # Check the shell for errors # + ############################## + if [ ${ERROR_CODE} -eq 0 ]; then ######### # Error # ######### error "Found errors in [${LINTER_NAME}] linter!" - error "[${LINT_CMD}]" - error "Linter CMD:[${LINTER_COMMAND} ${FILE}]" + error "This file should have failed test case!" + error "Error code: ${ERROR_CODE}. Command run:${NC}[\$${LINT_CMD}]." # Increment the error count (("ERRORS_FOUND_${FILE_TYPE}++")) + else + ########### + # Success # + ########### + info " - File:${F[W]}[${FILE_NAME}]${F[B]} failed test case (Error code: ${ERROR_CODE}) with ${F[W]}[${LINTER_NAME}]${F[B]} successfully" fi ####################################################### # Store the linting as a temporary file in TAP format # ####################################################### if IsTAP; then - NotOkTap "${INDEX}" "${FILE}" "${TMPFILE}" + NotOkTap "${INDEX}" "${FILE_NAME}" "${TMPFILE}" AddDetailedMessageIfEnabled "${LINT_CMD}" "${TMPFILE}" fi - else - ########### - # Success # - ########### - info " - File:${F[W]}[${FILE_NAME}]${F[B]} was linted with ${F[W]}[${LINTER_NAME}]${F[B]} successfully" - - ####################################################### - # Store the linting as a temporary file in TAP format # - ####################################################### - if IsTAP; then - OkTap "${INDEX}" "${FILE}" "${TMPFILE}" - fi fi + debug "Error code: ${ERROR_CODE}. Command run:${NC}[\$${LINT_CMD}]." done ################################# @@ -303,411 +341,49 @@ function LintCodebase() { if IsTAP && [ ${INDEX} -gt 0 ]; then HeaderTap "${INDEX}" "${REPORT_OUTPUT_FILE}" cat "${TMPFILE}" >>"${REPORT_OUTPUT_FILE}" - fi - fi -} -################################################################################ -#### Function TestCodebase ##################################################### -function TestCodebase() { - #################### - # Pull in the vars # - #################### - FILE_TYPE="${1}" # Pull the variable and remove from array path (Example: JSON) - LINTER_NAME="${2}" # Pull the variable and remove from array path (Example: jsonlint) - LINTER_COMMAND="${3}" # Pull the variable and remove from array path (Example: jsonlint -c ConfigFile /path/to/file) - FILE_EXTENSIONS="${4}" # Pull the variable and remove from array path (Example: *.json) - INDIVIDUAL_TEST_FOLDER="${5}" # Folder for specific tests - TESTS_RAN=0 # Incremented when tests are ran, this will help find failed finds - debug "Running ${FILE_TYPE} test. Linter name: ${LINTER_NAME}, linter command: ${LINTER_COMMAND}, file extensions: ${FILE_EXTENSIONS}, individual test folder: ${INDIVIDUAL_TEST_FOLDER}" - - ################ - # print header # - ################ - info "----------------------------------------------" - info "----------------------------------------------" - info "Testing Codebase [${FILE_TYPE}] files..." - info "----------------------------------------------" - info "----------------------------------------------" - - ##################################### - # Validate we have linter installed # - ##################################### - # Edgecase for Lintr as it is a Package for R - if [[ ${LINTER_NAME} == *"lintr"* ]]; then - VALIDATE_INSTALL_CMD=$(command -v "R" 2>&1) - else - VALIDATE_INSTALL_CMD=$(command -v "${LINTER_NAME}" 2>&1) - fi - - ####################### - # Load the error code # - ####################### - ERROR_CODE=$? - - ############################## - # Check the shell for errors # - ############################## - if [ ${ERROR_CODE} -ne 0 ]; then - # Failed - error "Failed to find [${LINTER_NAME}] in system!" - fatal "[${VALIDATE_INSTALL_CMD}]" - else - # Success - info "Successfully found binary for ${F[W]}[${LINTER_NAME}]${F[B]} in system location: ${F[W]}[${VALIDATE_INSTALL_CMD}]" - fi - - ########################## - # Initialize empty Array # - ########################## - LIST_FILES=() - - ################################# - # Get list of all files to lint # - ################################# - mapfile -t LIST_FILES < <(find "${GITHUB_WORKSPACE}/${TEST_CASE_FOLDER}/${INDIVIDUAL_TEST_FOLDER}" \ - -path "*/node_modules" -prune -o \ - -path "*/.venv" -prune -o \ - -path "*/.git" -prune -o \ - -path "*/.rbenv" -prune -o \ - -path "*/.terragrunt-cache" -prune -o \ - -type f -regex "${FILE_EXTENSIONS}" \ - ! -path "${GITHUB_WORKSPACE}/${TEST_CASE_FOLDER}/ansible/ghe-initialize/*" | sort 2>&1) - - ######################################## - # Prepare context if TAP output format # - ######################################## - if IsTAP; then - TMPFILE=$(mktemp -q "/tmp/super-linter-${FILE_TYPE}.XXXXXX") - mkdir -p "${REPORT_OUTPUT_FOLDER}" - REPORT_OUTPUT_FILE="${REPORT_OUTPUT_FOLDER}/super-linter-${FILE_TYPE}.${OUTPUT_FORMAT}" - fi - - ################## - # Lint the files # - ################## - for FILE in "${LIST_FILES[@]}"; do - ##################### - # Get the file name # - ##################### - FILE_NAME=$(basename "${FILE}" 2>&1) - DIR_NAME=$(dirname "${FILE}" 2>&1) - - ############################ - # Get the file pass status # - ############################ - # Example: markdown_good_1.md -> good - FILE_STATUS=$(echo "${FILE_NAME}" | cut -f2 -d'_') - - ######################################################### - # If not found, assume it should be linted successfully # - ######################################################### - if [ -z "${FILE_STATUS}" ] || [[ ${FILE} == *"README"* ]]; then - ################################## - # Set to good for proper linting # - ################################## - FILE_STATUS="good" - fi - - ############## - # File print # - ############## - info "---------------------------" - info "File:[${FILE}]" - - ######################## - # Set the lint command # - ######################## - LINT_CMD='' - - ####################################### - # Check if docker and get folder name # - ####################################### - if [[ ${FILE_TYPE} == *"DOCKER"* ]]; then - if [[ ${FILE} == *"good"* ]]; then - ############# - # Good file # - ############# - FILE_STATUS='good' - else - ############ - # Bad file # - ############ - FILE_STATUS='bad' + if [ "${TEST_CASE_RUN}" = "true" ]; then + ######################################################################## + # If expected TAP report exists then compare with the generated report # + ######################################################################## + EXPECTED_FILE="${WORKSPACE_PATH}/${INDIVIDUAL_TEST_FOLDER}/reports/expected-${FILE_TYPE}.tap" + if [ -e "${EXPECTED_FILE}" ]; then + TMPFILE=$(mktemp -q "/tmp/diff-${FILE_TYPE}.XXXXXX") + ## Ignore white spaces, case sensitive + if ! diff -a -w -i "${EXPECTED_FILE}" "${REPORT_OUTPUT_FILE}" >"${TMPFILE}" 2>&1; then + ############################################# + # We failed to compare the reporting output # + ############################################# + error "Failed to assert TAP output:[${LINTER_NAME}]"! + info "Please validate the asserts!" + cat "${TMPFILE}" + exit 1 + else + # Success + info "Successfully validation in the expected TAP format for ${F[W]}[${LINTER_NAME}]" + fi + else + warn "No TAP expected file found at:[${EXPECTED_FILE}]" + info "skipping report assertions" + ##################################### + # Append the file type to the array # + ##################################### + WARNING_ARRAY_TEST+=("${FILE_TYPE}") + fi fi fi - ##################### - # Check for ansible # - ##################### - if [[ ${FILE_TYPE} == "ANSIBLE" ]]; then - ######################################### - # Make sure we don't lint certain files # - ######################################### - if [[ ${FILE} == *"vault.yml"* ]] || [[ ${FILE} == *"galaxy.yml"* ]]; then - # This is a file we don't look at - continue - fi - - ################################ - # Lint the file with the rules # - ################################ - LINT_CMD=$( - cd "${GITHUB_WORKSPACE}/${TEST_CASE_FOLDER}/${INDIVIDUAL_TEST_FOLDER}" || exit - ${LINTER_COMMAND} "${FILE}" 2>&1 - ) - elif [[ ${FILE_TYPE} == "POWERSHELL" ]] || [[ ${FILE_TYPE} == "ARM" ]]; then - ################################ - # Lint the file with the rules # - ################################ - # Need to run PowerShell commands using pwsh -c, also exit with exit code from inner subshell - LINT_CMD=$( - cd "${GITHUB_WORKSPACE}/${TEST_CASE_FOLDER}" || exit - pwsh -NoProfile -NoLogo -Command "${LINTER_COMMAND} ${FILE}; if (\${Error}.Count) { exit 1 }" - exit $? 2>&1 - ) - ############################################################################### - # Corner case for groovy as we have to pass it as path and file in ant format # - ############################################################################### - elif [[ ${FILE_TYPE} == "GROOVY" ]]; then - ####################################### - # Lint the file with the updated path # - ####################################### - LINT_CMD=$( - cd "${GITHUB_WORKSPACE}" || exit - ${LINTER_COMMAND} --path "${DIR_NAME}" --files "$FILE_NAME" 2>&1 - ) - ############################################################################### - # Corner case for R as we have to pass it to R # - ############################################################################### - elif [[ ${FILE_TYPE} == "R" ]]; then - ####################################### - # Lint the file with the updated path # - ####################################### - LINT_CMD=$( - cd "${GITHUB_WORKSPACE}" || exit - R --slave -e "errors <- lintr::lint('$FILE');print(errors);quit(save = 'no', status = if (length(errors) > 0) 1 else 0)" 2>&1 - ) - ######################################################### - # Corner case for C# as it writes to tty and not stdout # - ######################################################### - elif [[ ${FILE_TYPE} == "CSHARP" ]]; then - LINT_CMD=$( - cd "${DIR_NAME}" || exit - ${LINTER_COMMAND} "${FILE_NAME}" | tee /dev/tty2 2>&1 - exit "${PIPESTATUS[0]}" - ) - else - ################################ - # Lint the file with the rules # - ################################ - LINT_CMD=$( - cd "${GITHUB_WORKSPACE}/${TEST_CASE_FOLDER}" || exit - ${LINTER_COMMAND} "${FILE}" 2>&1 - ) - fi - - ####################### - # Load the error code # - ####################### - ERROR_CODE=$? - - ######################################## - # Increment counter that check was ran # - ######################################## - (("TESTS_RAN++")) - - ######################################## - # Check for if it was supposed to pass # - ######################################## - if [[ ${FILE_STATUS} == "good" ]]; then - ############################## - # Check the shell for errors # - ############################## - if [ ${ERROR_CODE} -ne 0 ]; then - ######### - # Error # - ######### - error "Found errors in [${LINTER_NAME}] linter!" - error "[${LINT_CMD}]" - error "Linter CMD:[${LINTER_COMMAND} ${FILE}]" - # Increment the error count - (("ERRORS_FOUND_${FILE_TYPE}++")) - else - ########### - # Success # - ########### - info " - File:${F[W]}[${FILE_NAME}]${F[B]} was linted with ${F[W]}[${LINTER_NAME}]${F[B]} successfully" - fi - ####################################################### - # Store the linting as a temporary file in TAP format # - ####################################################### - if IsTAP; then - OkTap "${TESTS_RAN}" "${FILE_NAME}" "${TMPFILE}" - fi - else - ####################################### - # File status = bad, this should fail # - ####################################### - ############################## - # Check the shell for errors # - ############################## - if [ ${ERROR_CODE} -eq 0 ]; then - ######### - # Error # - ######### - error "Found errors in [${LINTER_NAME}] linter!" - error "This file should have failed test case!" - error "Command run:${NC}[\$${LINT_CMD}]" - error "[${LINT_CMD}]" - error "Linter CMD:[${LINTER_COMMAND} ${FILE}]" - # Increment the error count - (("ERRORS_FOUND_${FILE_TYPE}++")) - else - ########### - # Success # - ########### - info " - File:${F[W]}[${FILE_NAME}]${F[B]} failed test case with ${F[W]}[${LINTER_NAME}]${F[B]} successfully" - fi - ####################################################### - # Store the linting as a temporary file in TAP format # - ####################################################### - if IsTAP; then - NotOkTap "${TESTS_RAN}" "${FILE_NAME}" "${TMPFILE}" - AddDetailedMessageIfEnabled "${LINT_CMD}" "${TMPFILE}" - fi - fi - done - - ########################################################################### - # Generate report in TAP format and validate with the expected TAP output # - ########################################################################### - if IsTAP && [ ${TESTS_RAN} -gt 0 ]; then - HeaderTap "${TESTS_RAN}" "${REPORT_OUTPUT_FILE}" - cat "${TMPFILE}" >>"${REPORT_OUTPUT_FILE}" - - ######################################################################## - # If expected TAP report exists then compare with the generated report # - ######################################################################## - EXPECTED_FILE="${GITHUB_WORKSPACE}/${TEST_CASE_FOLDER}/${INDIVIDUAL_TEST_FOLDER}/reports/expected-${FILE_TYPE}.tap" - if [ -e "${EXPECTED_FILE}" ]; then - TMPFILE=$(mktemp -q "/tmp/diff-${FILE_TYPE}.XXXXXX") - ## Ignore white spaces, case sensitive - if ! diff -a -w -i "${EXPECTED_FILE}" "${REPORT_OUTPUT_FILE}" >"${TMPFILE}" 2>&1; then - ############################################# - # We failed to compare the reporting output # - ############################################# - error "Failed to assert TAP output:[${LINTER_NAME}]"! - info "Please validate the asserts!" - cat "${TMPFILE}" - exit 1 - else - # Success - info "Successfully validation in the expected TAP format for ${F[W]}[${LINTER_NAME}]" - fi - else - warn "No TAP expected file found at:[${EXPECTED_FILE}]" - info "skipping report assertions" - ##################################### - # Append the file type to the array # - ##################################### - WARNING_ARRAY_TEST+=("${FILE_TYPE}") + ############################## + # Validate we ran some tests # + ############################## + if [ "${TEST_CASE_RUN}" = "true" ] && [ "${INDEX}" -eq 0 ]; then + ################################################# + # We failed to find files and no tests were ran # + ################################################# + error "Failed to find any tests ran for the Linter:[${LINTER_NAME}]"! + fatal "Please validate logic or that tests exist!" fi fi - - ############################## - # Validate we ran some tests # - ############################## - if [ "${TESTS_RAN}" -eq 0 ]; then - ################################################# - # We failed to find files and no tests were ran # - ################################################# - error "Failed to find any tests ran for the Linter:[${LINTER_NAME}]"! - fatal "Please validate logic or that tests exist!" - fi -} -################################################################################ -#### Function RunTestCases ##################################################### -function RunTestCases() { - # This loop will run the test cases and exclude user code - # This is called from the automation process to validate new code - # When a PR is opened, the new code is validated with the default branch - # version of linter.sh, and a new container is built with the latest codebase - # for testing. That container is spun up, and ran, - # with the flag: TEST_CASE_RUN=true - # So that the new code can be validated against the test cases - - ################# - # Header prints # - ################# - info "----------------------------------------------" - info "-------------- TEST CASE RUN -----------------" - info "----------------------------------------------" - - ####################### - # Test case languages # - ####################### - # TestCodebase "Language" "Linter" "Linter-command" "Regex to find files" "Test Folder" - TestCodebase "ANSIBLE" "ansible-lint" "ansible-lint -v -c ${ANSIBLE_LINTER_RULES}" ".*\.\(yml\|yaml\)\$" "ansible" - TestCodebase "ARM" "arm-ttk" "Import-Module ${ARM_TTK_PSD1} ; \${config} = \$(Import-PowerShellDataFile -Path ${ARM_LINTER_RULES}) ; Test-AzTemplate @config -TemplatePath" ".*\.\(json\)\$" "arm" - TestCodebase "BASH" "shellcheck" "shellcheck --color --external-sources" ".*\.\(sh\|bash\|dash\|ksh\)\$" "shell" - TestCodebase "BASH_EXEC" "bash-exec" "bash-exec" ".*\.\(sh\|bash\|dash\|ksh\)\$" "shell" - TestCodebase "CLOUDFORMATION" "cfn-lint" "cfn-lint --config-file ${CLOUDFORMATION_LINTER_RULES}" ".*\.\(json\|yml\|yaml\)\$" "cloudformation" - TestCodebase "CLOJURE" "clj-kondo" "clj-kondo --config ${CLOJURE_LINTER_RULES} --lint" ".*\.\(clj\|cljs\|cljc\|edn\)\$" "clojure" - TestCodebase "COFFEESCRIPT" "coffeelint" "coffeelint -f ${COFFEESCRIPT_LINTER_RULES}" ".*\.\(coffee\)\$" "coffeescript" - TestCodebase "CSHARP" "dotnet-format" "dotnet-format --check --folder --exclude / --include" ".*\.\(cs\)\$" "csharp" - TestCodebase "CSS" "stylelint" "stylelint --config ${CSS_LINTER_RULES}" ".*\.\(css\|scss\|sass\)\$" "css" - TestCodebase "DART" "dart" "dartanalyzer --fatal-infos --fatal-warnings --options ${DART_LINTER_RULES}" ".*\.\(dart\)\$" "dart" - TestCodebase "DOCKERFILE" "dockerfilelint" "dockerfilelint -c ${DOCKERFILE_LINTER_RULES}" ".*\(Dockerfile\)\$" "docker" - TestCodebase "DOCKERFILE_HADOLINT" "hadolint" "hadolint -c ${DOCKERFILE_HADOLINT_LINTER_RULES}" ".*\(Dockerfile\)\$" "docker" - TestCodebase "EDITORCONFIG" "editorconfig-checker" "editorconfig-checker" ".*\.ext$" "editorconfig-checker" - TestCodebase "ENV" "dotenv-linter" "dotenv-linter" ".*\.\(env\)\$" "env" - TestCodebase "GO" "golangci-lint" "golangci-lint run -c ${GO_LINTER_RULES}" ".*\.\(go\)\$" "golang" - TestCodebase "GROOVY" "npm-groovy-lint" "npm-groovy-lint -c $GROOVY_LINTER_RULES --failon warning" ".*\.\(groovy\|jenkinsfile\|gradle\|nf\)\$" "groovy" - TestCodebase "HTML" "htmlhint" "htmlhint --config ${HTML_LINTER_RULES}" ".*\.\(html\)\$" "html" - TestCodebase "JAVA" "checkstyle" "java -jar /usr/bin/checkstyle -c ${JAVA_LINTER_RULES}" ".*\.\(java\)\$" "java" - TestCodebase "JAVASCRIPT_ES" "eslint" "eslint --no-eslintrc -c ${JAVASCRIPT_ES_LINTER_RULES}" ".*\.\(js\)\$" "javascript" - TestCodebase "JAVASCRIPT_STANDARD" "standard" "standard ${JAVASCRIPT_STANDARD_LINTER_RULES}" ".*\.\(js\)\$" "javascript" - TestCodebase "JSON" "jsonlint" "jsonlint" ".*\.\(json\)\$" "json" - TestCodebase "KUBERNETES_KUBEVAL" "kubeval" "kubeval --strict" ".*\.\(yml\|yaml\)\$" "kubeval" - TestCodebase "KOTLIN" "ktlint" "ktlint" ".*\.\(kt\|kts\)\$" "kotlin" - TestCodebase "LATEX" "chktex" "chktex -q -l ${LATEX_LINTER_RULES}" ".*\.\(tex\)\$" "latex" - TestCodebase "LUA" "lua" "luacheck" ".*\.\(lua\)\$" "lua" - TestCodebase "MARKDOWN" "markdownlint" "markdownlint -c ${MARKDOWN_LINTER_RULES}" ".*\.\(md\)\$" "markdown" - TestCodebase "PERL" "perl" "perlcritic" ".*\.\(pl\|pm\|t\)\$" "perl" - TestCodebase "PHP_BUILTIN" "php" "php -l" ".*\.\(php\)\$" "php" - TestCodebase "PHP_PHPCS" "phpcs" "phpcs --standard=${PHP_PHPCS_LINTER_RULES}" ".*\.\(php\)\$" "php" - TestCodebase "PHP_PHPSTAN" "phpstan" "phpstan analyse --no-progress --no-ansi -c ${PHP_PHPSTAN_LINTER_RULES}" ".*\.\(php\)\$" "php" - TestCodebase "PHP_PSALM" "psalm" "psalm --config=${PHP_PSALM_LINTER_RULES}" ".*\.\(php\)\$" "php" - TestCodebase "OPENAPI" "spectral" "spectral lint -r ${OPENAPI_LINTER_RULES}" ".*\.\(ymlopenapi\|jsonopenapi\)\$" "openapi" - TestCodebase "POWERSHELL" "pwsh" "Invoke-ScriptAnalyzer -EnableExit -Settings ${POWERSHELL_LINTER_RULES} -Path" ".*\.\(ps1\|psm1\|psd1\|ps1xml\|pssc\|psrc\|cdxml\)\$" "powershell" - TestCodebase "PROTOBUF" "protolint" "protolint lint --config_path ${PROTOBUF_LINTER_RULES}" ".*\.\(proto\)\$" "protobuf" - TestCodebase "PYTHON_BLACK" "black" "black --config ${PYTHON_BLACK_LINTER_RULES} --diff --check" ".*\.\(py\)\$" "python" - TestCodebase "PYTHON_FLAKE8" "flake8" "flake8 --config ${PYTHON_FLAKE8_LINTER_RULES}" ".*\.\(py\)\$" "python" - TestCodebase "PYTHON_PYLINT" "pylint" "pylint --rcfile ${PYTHON_PYLINT_LINTER_RULES}" ".*\.\(py\)\$" "python" - TestCodebase "R" "lintr" "lintr::lint()" ".*\.\(r\|rmd\)\$" "r" - TestCodebase "RAKU" "raku" "raku -c" ".*\.\(raku\|rakumod\|rakutest\|pm6\|pl6\|p6\)\$" "raku" - TestCodebase "RUBY" "rubocop" "rubocop -c ${RUBY_LINTER_RULES}" ".*\.\(rb\)\$" "ruby" - TestCodebase "SHELL_SHFMT" "shfmt" "shfmt -d" ".*\.\(sh\|bash\|dash\|ksh\)\$" "shell_shfmt" - TestCodebase "SNAKEMAKE_LINT" "snakemake" "snakemake --lint -s" ".*\.\(smk\)\$" "snakemake" - TestCodebase "SNAKEMAKE_SNAKEFMT" "snakefmt" "snakefmt --config ${SNAKEMAKE_SNAKEFMT_LINTER_RULES} --check --compact-diff" ".*\.\(smk\)\$" "snakemake" - TestCodebase "STATES" "asl-validator" "asl-validator --json-path" ".*\.\(json\)\$" "states" - TestCodebase "SQL" "sql-lint" "sql-lint --config ${SQL_LINTER_RULES}" ".*\.\(sql\)\$" "sql" - TestCodebase "TEKTON" "tekton-lint" "tekton-lint" ".*\.\(yml\|yaml\)\$" "tekton" - TestCodebase "TERRAFORM" "tflint" "tflint -c ${TERRAFORM_LINTER_RULES}" ".*\.\(tf\)\$" "terraform" - TestCodebase "TERRAFORM_TERRASCAN" "terrascan" "terrascan scan -p /root/.terrascan/pkg/policies/opa/rego/ -t aws -f " ".*\.\(tf\)\$" "terraform_terrascan" - TestCodebase "TERRAGRUNT" "terragrunt" "terragrunt hclfmt --terragrunt-check --terragrunt-hclfmt-file " ".*\.\(hcl\)\$" "terragrunt" - TestCodebase "TYPESCRIPT_ES" "eslint" "eslint --no-eslintrc -c ${TYPESCRIPT_ES_LINTER_RULES}" ".*\.\(ts\)\$" "typescript" - TestCodebase "TYPESCRIPT_STANDARD" "standard" "standard --parser @typescript-eslint/parser --plugin @typescript-eslint/eslint-plugin ${TYPESCRIPT_STANDARD_LINTER_RULES}" ".*\.\(ts\)\$" "typescript" - TestCodebase "XML" "xmllint" "xmllint" ".*\.\(xml\)\$" "xml" - TestCodebase "YAML" "yamllint" "yamllint -c ${YAML_LINTER_RULES}" ".*\.\(yml\|yaml\)\$" "yaml" - - ################# - # Footer prints # - ################# - # Call the footer to display run information - # and exit with error code - Footer } ################################################################################ #### Function LintAnsibleFiles ################################################# @@ -732,29 +408,6 @@ function LintAnsibleFiles() { ###################### LINTER_NAME="ansible-lint" - ########################################### - # Validate we have ansible-lint installed # - ########################################### - VALIDATE_INSTALL_CMD=$(command -v "${LINTER_NAME}" 2>&1) - - ####################### - # Load the error code # - ####################### - ERROR_CODE=$? - - ############################## - # Check the shell for errors # - ############################## - if [ ${ERROR_CODE} -ne 0 ]; then - # Failed - error "Failed to find ${LINTER_NAME} in system!" - fatal "[${VALIDATE_INSTALL_CMD}]" - else - # Success - debug "Successfully found binary in system" - debug "Location:[${VALIDATE_INSTALL_CMD}]" - fi - ########################## # Initialize empty Array # ##########################