diff --git a/docs/rules.md b/docs/rules.md index 858827e3..87eb3bf2 100644 --- a/docs/rules.md +++ b/docs/rules.md @@ -24,6 +24,7 @@ Below is a complete list of rules that Repolinter can run, along with their conf - [`license-detectable-by-licensee`](#license-detectable-by-licensee) - [`best-practices-badge-present`](#best-practices-badge-present) - [`any-file-contents`](#any-file-contents) + - [`file-or-directory-existence`](#file-or-directory-existence) ## Reference @@ -218,3 +219,13 @@ Checks if the contents of at least one file in a given list match a given regula | `flags` | No | `string` | `""` | The flags to use for the regular expression in `content` (ex. `"i"` for case insensitivity). | | `human-readable-content` | No | `string` | The regular expression in `content` | The string to print instead of the regular expression when generating human-readable output. | | `fail-on-non-existent` | No | `boolean` | `false` | Set to `true` to disable passing if no files are found from `globsAll`. | + +### `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..f12452b4 --- /dev/null +++ b/rules/file-or-directory-existence-config.json @@ -0,0 +1,20 @@ +{ + "$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"] } + ] + } + \ No newline at end of file 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 6d9708bf..eeaee352 100644 --- a/rules/rules.js +++ b/rules/rules.js @@ -19,5 +19,6 @@ module.exports = [ 'git-working-tree', 'license-detectable-by-licensee', 'json-schema-passes', - 'any-file-contents' + 'any-file-contents', + 'file-or-directory-existence' ] diff --git a/rulesets/schema.json b/rulesets/schema.json index ec95b3b6..b3762fd9 100644 --- a/rulesets/schema.json +++ b/rulesets/schema.json @@ -243,6 +243,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' + }) + }) + }) +})