From 85265984881629925f4294d73dbdc9c6672db959 Mon Sep 17 00:00:00 2001 From: Brend Smits Date: Fri, 31 Mar 2023 11:15:05 +0200 Subject: [PATCH] feat: add file-or-directory-existence rule Co-authored-by: Dragos Serban Signed-off-by: Brend Smits --- docs/rules.md | 11 ++ rules/file-or-directory-existence-config.json | 19 ++++ rules/file-or-directory-existence.js | 39 +++++++ rules/rules.js | 3 +- rulesets/schema.json | 12 ++ .../file_or_directory_existence_tests.js | 107 ++++++++++++++++++ 6 files changed, 190 insertions(+), 1 deletion(-) create mode 100644 rules/file-or-directory-existence-config.json create mode 100644 rules/file-or-directory-existence.js create mode 100644 tests/rules/file_or_directory_existence_tests.js diff --git a/docs/rules.md b/docs/rules.md index 7f040d38..1704472e 100644 --- a/docs/rules.md +++ b/docs/rules.md @@ -25,6 +25,7 @@ Below is a complete list of rules that Repolinter can run, along with their conf - [`large-file`](#large-file) - [`license-detectable-by-licensee`](#license-detectable-by-licensee) - [`best-practices-badge-present`](#best-practices-badge-present) + - [`file-or-directory-existence`](#file-or-directory-existence) ## Reference @@ -229,3 +230,13 @@ Check Best Practices Badge is present in README. Optionally check a certain badg | Input | Required | Type | Default | Description | | ------------ | -------- | ---------- | ------- | ------------------------------------------------------------------ | | `minPercentage` | No | `integer` | `null` | Minimum [Tiered Percentage](https://github.com/coreinfrastructure/best-practices-badge/blob/main/doc/api.md#tiered-percentage-in-openssf-best-practices-badge) accomplished by project. `passing=100`, `silver=200`, `gold=300`, set to `0` or `null` to disable check. | + +### `file-or-directory-existence` + +Checks the existence of a given file OR directory. + +| Input | Required | Type | Default | Description | +| -------------- | -------- | ---------- | ------- | -------------------------------------------------------------------------------------------------- | +| `globsAny` | **Yes** | `string[]` | | A list of globs to search for. This rule passes if at least one glob returns a file or directory. | +| `nocase` | No | `boolean` | `false` | Set to `true` to perform an case insensitive search. | +| `fail-message` | No | `string` | `""` | The string to print if file or directory does not exist, used to create human-readable error messages. | diff --git a/rules/file-or-directory-existence-config.json b/rules/file-or-directory-existence-config.json new file mode 100644 index 00000000..0653280c --- /dev/null +++ b/rules/file-or-directory-existence-config.json @@ -0,0 +1,19 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "https://raw.githubusercontent.com/todogroup/repolinter/master/rules/file-or-directory-existence-config.json", + "type": "object", + "properties": { + "nocase": { + "type": "boolean", + "default": false + }, + "globsAny": { + "type": "array", + "items": { "type": "string" } + }, + "fail-message": { "type": "string" } + }, + "oneOf": [ + { "required": ["globsAny"] } + ] +} diff --git a/rules/file-or-directory-existence.js b/rules/file-or-directory-existence.js new file mode 100644 index 00000000..6436ce9c --- /dev/null +++ b/rules/file-or-directory-existence.js @@ -0,0 +1,39 @@ +// Copyright 2017 TODO Group. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +const Result = require('../lib/result') + +async function fileOrDirectoryExistence(fs, options) { + const fileOrDirectoryExists = await fs.findFirst( + options.globsAny, + options.nocase + ) + + const passed = !!fileOrDirectoryExists + + return passed + ? new Result( + '', + [ + { + passed: true, + path: fileOrDirectoryExists, + message: 'Found file or directory matching the specified patterns' + } + ], + true + ) + : new Result( + `${ + options['fail-message'] !== undefined + ? options['fail-message'] + '. ' + : '' + }Did not find a file or directory matching the specified patterns`, + options.globsAny.map(f => { + return { passed: false, pattern: f } + }), + false + ) +} + +module.exports = fileOrDirectoryExistence diff --git a/rules/rules.js b/rules/rules.js index 0acb0134..1cd080e6 100644 --- a/rules/rules.js +++ b/rules/rules.js @@ -20,5 +20,6 @@ module.exports = { 'git-working-tree': require('./git-working-tree'), 'large-file': require('./large-file'), 'license-detectable-by-licensee': require('./license-detectable-by-licensee'), - 'json-schema-passes': require('./json-schema-passes') + 'json-schema-passes': require('./json-schema-passes'), + 'file-or-directory-existence': require('./file-or-directory-existence') } diff --git a/rulesets/schema.json b/rulesets/schema.json index 8c469454..c6e37ac0 100644 --- a/rulesets/schema.json +++ b/rulesets/schema.json @@ -253,6 +253,18 @@ } } } + }, + { + "if": { + "properties": { "type": { "const": "file-or-directory-existence" } } + }, + "then": { + "properties": { + "options": { + "$ref": "../rules/file-or-directory-existence-config.json" + } + } + } } ] }, diff --git a/tests/rules/file_or_directory_existence_tests.js b/tests/rules/file_or_directory_existence_tests.js new file mode 100644 index 00000000..7a35b810 --- /dev/null +++ b/tests/rules/file_or_directory_existence_tests.js @@ -0,0 +1,107 @@ +// Copyright 2017 TODO Group. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +const chai = require('chai') +const path = require('path') +const FileSystem = require('../../lib/file_system') + +const expect = chai.expect + +describe('rule', () => { + describe('file_or_directory_existence', () => { + const fileOrDirectoryExistence = require('../../rules/file-existence') + const fs = new FileSystem(path.resolve('./tests/rules'), []) + + it('returns a passed result if both files and directories exist matching the given pattern', async () => { + /** @type {any} */ + const ruleoptsImageFirst = { + globsAny: ['image_for_test.png', 'markup_test_files/'] + } + + const ruleoptsDirFirst = { + globsAny: ['markup_test_files/', 'image_for_test.png'] + } + + const actualImageFirst = await fileOrDirectoryExistence( + fs, + ruleoptsImageFirst + ) + + expect(actualImageFirst.passed).to.equal(true) + expect(actualImageFirst.targets).to.have.length(1) + expect(actualImageFirst.targets[0]).to.deep.include({ + passed: true, + path: 'image_for_test.png' + }) + + const actualDirFirst = await fileOrDirectoryExistence( + fs, + ruleoptsDirFirst + ) + + expect(actualDirFirst.passed).to.equal(true) + expect(actualDirFirst.targets).to.have.length(1) + expect(actualDirFirst.targets[0]).to.deep.include({ + passed: true, + path: 'markup_test_files/' + }) + }) + + it('returns a passed result if only files exist matching the given pattern', async () => { + /** @type {any} */ + const ruleopts = { + globsAny: ['markup_test_files_nonexistent/', 'image_for_test.png'] + } + + const actual = await fileOrDirectoryExistence(fs, ruleopts) + + expect(actual.passed).to.equal(true) + expect(actual.targets).to.have.length(1) + expect(actual.targets[0]).to.deep.include({ + passed: true, + path: 'image_for_test.png' + }) + }) + + it('returns a passed result if only directories exist matching the given pattern', async () => { + /** @type {any} */ + const ruleopts = { + globsAny: [ + 'image_for_test_nonexistent.png', + 'another_nonexistent_image.jpg', + 'markup_test_files/' + ] + } + + const actual = await fileOrDirectoryExistence(fs, ruleopts) + + expect(actual.passed).to.equal(true) + expect(actual.targets).to.have.length(1) + expect(actual.targets[0]).to.deep.include({ + passed: true, + path: 'markup_test_files/' + }) + }) + + it('returns a failed result if neither files or directories exist matching the given pattern', async () => { + /** @type {any} */ + const ruleopts = { + globsAny: [ + 'image_for_test_nonexistent.png', + 'a_nonexistent_directory/', + 'another_nonexistent_image.jpg', + 'markup_test_files_nonexistent/' + ] + } + + const actual = await fileOrDirectoryExistence(fs, ruleopts) + + expect(actual.passed).to.equal(false) + expect(actual.targets).to.have.length(4) + expect(actual.targets[0]).to.deep.include({ + passed: false, + pattern: 'image_for_test_nonexistent.png' + }) + }) + }) +})