Skip to content

Commit

Permalink
Fix LintResults coding issues + rename CheckInfo
Browse files Browse the repository at this point in the history
  • Loading branch information
Jeehut committed Sep 7, 2021
1 parent e7cc711 commit 8b4416d
Show file tree
Hide file tree
Showing 15 changed files with 373 additions and 170 deletions.
4 changes: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ If needed, pluralize to `Tasks`, `PRs` or `Authors` and list multiple entries se
### Added
- Made `AutoCorrection` expressible by Dictionary literals and updated the `README.md` accordingly.
Issue: [#5](https://github.com/Flinesoft/AnyLint/issues/5) | PR: [#11](https://github.com/Flinesoft/AnyLint/pull/11) | Author: [Cihat Gündüz](https://github.com/Jeehut)
- Added option to skip checks within file contents by specifying `AnyLint.skipHere: <CheckInfo.ID>` or `AnyLint.skipInFile: <All or CheckInfo.ID>`. Checkout the [Skip file content checks](https://github.com/Flinesoft/AnyLint#skip-file-content-checks) README section for more info.
- Added option to skip checks within file contents by specifying `AnyLint.skipHere: <Check.ID>` or `AnyLint.skipInFile: <All or Check.ID>`. Checkout the [Skip file content checks](https://github.com/Flinesoft/AnyLint#skip-file-content-checks) README section for more info.
Issue: [#9](https://github.com/Flinesoft/AnyLint/issues/9) | PR: [#12](https://github.com/Flinesoft/AnyLint/pull/12) | Author: [Cihat Gündüz](https://github.com/Jeehut)

## [0.2.0] - 2020-04-10
Expand All @@ -122,7 +122,7 @@ If needed, pluralize to `Tasks`, `PRs` or `Authors` and list multiple entries se
- Added two simple lint check examples in first code sample in README. (Thanks for the pointer, [Dave Verwer](https://github.com/daveverwer)!)
Author: [Cihat Gündüz](https://github.com/Jeehut)
### Changed
- Changed `CheckInfo` id casing convention from snake_case to UpperCamelCase in `blank` template.
- Changed `Check` id casing convention from snake_case to UpperCamelCase in `blank` template.
Author: [Cihat Gündüz](https://github.com/Jeehut)

## [0.1.0] - 2020-03-22
Expand Down
28 changes: 14 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,9 +205,9 @@ The `.anchorsMatchLines` option is always activated on literal usage as we stron

</details>

#### CheckInfo
#### Check

A `CheckInfo` contains the basic information about a lint check. It consists of:
A `Check` contains the basic information about a lint check. It consists of:

1. `id`: The identifier of your lint check. For example: `EmptyTodo`
2. `hint`: The hint explaining the cause of the violation or the steps to fix it.
Expand All @@ -217,8 +217,8 @@ While there is an initializer available, we recommend using a String Literal ins

```swift
// accepted structure: <id>(@<severity>): <hint>
let checkInfo: CheckInfo = "ReadmePath: The README file should be named exactly `README.md`."
let checkInfoCustomSeverity: CheckInfo = "ReadmePath@warning: The README file should be named exactly `README.md`."
let check: Check = "ReadmePath: The README file should be named exactly `README.md`."
let checkCustomSeverity: Check = "ReadmePath@warning: The README file should be named exactly `README.md`."
```
#### AutoCorrection
Expand All @@ -241,12 +241,12 @@ let example: AutoCorrection = ["before": "Lisence", "after": "License"]

AnyLint has rich support for checking the contents of a file using a regex. The design follows the approach "make simple things simple and hard things possible". Thus, let's explain the `checkFileContents` method with a simple and a complex example.

In its simplest form, the method just requires a `checkInfo` and a `regex`:
In its simplest form, the method just requires a `check` and a `regex`:

```swift
// MARK: EmptyTodo
try Lint.checkFileContents(
checkInfo: "EmptyTodo: TODO comments should not be empty.",
check: "EmptyTodo: TODO comments should not be empty.",
regex: #"// TODO: *\n"#
)
```
Expand All @@ -271,7 +271,7 @@ let swiftTestFiles: Regex = #"Tests/.*\.swift"#
// MARK: - Checks
// MARK: empty_todo
try Lint.checkFileContents(
checkInfo: "EmptyTodo: TODO comments should not be empty.",
check: "EmptyTodo: TODO comments should not be empty.",
regex: #"// TODO: *\n"#,
matchingExamples: ["// TODO:\n"],
nonMatchingExamples: ["// TODO: not yet implemented\n"],
Expand Down Expand Up @@ -302,7 +302,7 @@ let swiftTestFiles: Regex = #"Tests/.*\.swift"#
// MARK: - Checks
// MARK: empty_method_body
try Lint.checkFileContents(
checkInfo: "EmptyMethodBody: Don't use whitespaces for the body of empty methods.",
check: "EmptyMethodBody: Don't use whitespaces for the body of empty methods.",
regex: [
"declaration": #"func [^\(\s]+\([^{]*\)"#,
"spacing": #"\s*"#,
Expand Down Expand Up @@ -347,7 +347,7 @@ While the `includeFilters` and `excludeFilters` arguments in the config file can

For such cases, there are **2 ways to skip checks** within the files themselves:

1. `AnyLint.skipHere: <CheckInfo.ID>`: Will skip the specified check(s) on the same line and the next line.
1. `AnyLint.skipHere: <Check.ID>`: Will skip the specified check(s) on the same line and the next line.

```swift
var x: Int = 5 // AnyLint.skipHere: MinVarNameLength
Expand All @@ -358,7 +358,7 @@ For such cases, there are **2 ways to skip checks** within the files themselves:
var x: Int = 5
```

2. `AnyLint.skipInFile: <All or CheckInfo.ID>`: Will skip `All` or specificed check(s) in the entire file.
2. `AnyLint.skipInFile: <All or Check.ID>`: Will skip `All` or specificed check(s) in the entire file.

```swift
// AnyLint.skipInFile: MinVarNameLength
Expand Down Expand Up @@ -398,10 +398,10 @@ TODO: Update to new custom script format supporting all languages as long as the

AnyLint allows you to do any kind of lint checks (thus its name) as it gives you the full power of the Swift programming language and it's packages [ecosystem](https://swiftpm.co/). The `customCheck` method needs to be used to profit from this flexibility. And it's actually the simplest of the three methods, consisting of only two parameters:

1. `checkInfo`: Provides some general information on the lint check.
1. `check`: Provides some general information on the lint check.
2. `customClosure`: Your custom logic which produces an array of `Violation` objects.

Note that the `Violation` type just holds some additional information on the file, matched string, location in the file and applied autocorrection and that all these fields are optional. It is a simple struct used by the AnyLint reporter for more detailed output, no logic attached. The only required field is the `CheckInfo` object which caused the violation.
Note that the `Violation` type just holds some additional information on the file, matched string, location in the file and applied autocorrection and that all these fields are optional. It is a simple struct used by the AnyLint reporter for more detailed output, no logic attached. The only required field is the `Check` object which caused the violation.

If you want to use regexes in your custom code, you can learn more about how you can match strings with a `Regex` object on [the HandySwift docs](https://github.com/Flinesoft/HandySwift#regex) (the project, the class was taken from) or read the [code documentation comments](https://github.com/Flinesoft/AnyLint/blob/main/Sources/Utility/Regex.swift).

Expand All @@ -418,7 +418,7 @@ Lint.logSummaryAndExit(arguments: CommandLine.arguments) {
// MARK: - Checks
// MARK: LinuxMainUpToDate
try Lint.customCheck(checkInfo: "LinuxMainUpToDate: The tests in Tests/LinuxMain.swift should be up-to-date.") { checkInfo in
try Lint.customCheck(check: "LinuxMainUpToDate: The tests in Tests/LinuxMain.swift should be up-to-date.") { check in
var violations: [Violation] = []
let linuxMainFilePath = "Tests/LinuxMain.swift"
Expand All @@ -436,7 +436,7 @@ Lint.logSummaryAndExit(arguments: CommandLine.arguments) {
if linuxMainContentsBeforeRegeneration != linuxMainContentsAfterRegeneration {
violations.append(
Violation(
checkInfo: checkInfo,
check: check,
filePath: linuxMainFilePath,
appliedAutoCorrection: AutoCorrection(
before: linuxMainContentsBeforeRegeneration,
Expand Down
4 changes: 2 additions & 2 deletions Sources/Checkers/FileContentsChecker.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ extension FileContentsChecker: Checker {
var newFileContents: String = fileContents
let linesInFile: [String] = fileContents.components(separatedBy: .newlines)

// skip check in file if contains `AnyLint.skipInFile: <All or CheckInfo.ID>`
// skip check in file if contains `AnyLint.skipInFile: <All or Check.ID>`
let skipInFileRegex = try Regex(#"AnyLint\.skipInFile:[^\n]*([, ]All[,\s]|[, ]\#(id)[,\s])"#)
guard !skipInFileRegex.matches(fileContents) else { continue }

Expand All @@ -45,7 +45,7 @@ extension FileContentsChecker: Checker {
for match in regex.matches(in: fileContents).reversed() {
let location = fileContents.fileLocation(of: match.range.lowerBound, filePath: filePath)

// skip found match if contains `AnyLint.skipHere: <CheckInfo.ID>` in same line or one line before
// skip found match if contains `AnyLint.skipHere: <Check.ID>` in same line or one line before
guard
!linesInFile.containsLine(at: [location.row! - 2, location.row! - 1], matchingRegex: skipHereRegex)
else { continue }
Expand Down
62 changes: 31 additions & 31 deletions Sources/Checkers/Lint.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public enum Lint {
/// Checks the contents of files.
///
/// - Parameters:
/// - checkInfo: The info object providing some general information on the lint check.
/// - check: The info object providing some general information on the lint check.
/// - regex: The regex to use for matching the contents of files. By defaults points to the start of the regex, unless you provide the named group 'pointer'.
/// - matchingExamples: An array of example contents where the `regex` is expected to trigger. Optionally, the expected pointer position can be marked with ↘.
/// - nonMatchingExamples: An array of example contents where the `regex` is expected not to trigger.
Expand All @@ -19,7 +19,7 @@ public enum Lint {
/// - autoCorrectExamples: An array of example structs with a `before` and an `after` String object to check if autocorrection works properly.
/// - repeatIfAutoCorrected: Repeat check if at least one auto-correction was applied in last run. Defaults to `false`.
public static func checkFileContents(
checkInfo: CheckInfo,
check: Check,
regex: Regex,
matchingExamples: [String] = [],
nonMatchingExamples: [String] = [],
Expand All @@ -29,19 +29,19 @@ public enum Lint {
autoCorrectExamples: [AutoCorrection] = [],
repeatIfAutoCorrected: Bool = false
) throws -> [Violation] {
validate(regex: regex, matchesForEach: matchingExamples, checkInfo: checkInfo)
validate(regex: regex, doesNotMatchAny: nonMatchingExamples, checkInfo: checkInfo)
validate(regex: regex, matchesForEach: matchingExamples, check: check)
validate(regex: regex, doesNotMatchAny: nonMatchingExamples, check: check)

validateParameterCombinations(
checkInfo: checkInfo,
check: check,
autoCorrectReplacement: autoCorrectReplacement,
autoCorrectExamples: autoCorrectExamples,
violateIfNoMatchesFound: nil
)

if let autoCorrectReplacement = autoCorrectReplacement {
validateAutocorrectsAll(
checkInfo: checkInfo,
check: check,
examples: autoCorrectExamples,
regex: regex,
autocorrectReplacement: autoCorrectReplacement
Expand All @@ -55,9 +55,9 @@ public enum Lint {
)

let violations = try FileContentsChecker(
id: checkInfo.id,
hint: checkInfo.hint,
severity: checkInfo.severity,
id: check.id,
hint: check.hint,
severity: check.severity,
regex: regex,
filePathsToCheck: filePathsToCheck,
autoCorrectReplacement: autoCorrectReplacement,
Expand All @@ -71,7 +71,7 @@ public enum Lint {
/// Checks the names of files.
///
/// - Parameters:
/// - checkInfo: The info object providing some general information on the lint check.
/// - check: The info object providing some general information on the lint check.
/// - regex: The regex to use for matching the paths of files. By defaults points to the start of the regex, unless you provide the named group 'pointer'.
/// - matchingExamples: An array of example paths where the `regex` is expected to trigger. Optionally, the expected pointer position can be marked with ↘.
/// - nonMatchingExamples: An array of example paths where the `regex` is expected not to trigger.
Expand All @@ -81,7 +81,7 @@ public enum Lint {
/// - autoCorrectExamples: An array of example structs with a `before` and an `after` String object to check if autocorrection works properly.
/// - violateIfNoMatchesFound: Inverts the violation logic to report a single violation if no matches are found instead of reporting a violation for each match.
public static func checkFilePaths(
checkInfo: CheckInfo,
check: Check,
regex: Regex,
matchingExamples: [String] = [],
nonMatchingExamples: [String] = [],
Expand All @@ -91,18 +91,18 @@ public enum Lint {
autoCorrectExamples: [AutoCorrection] = [],
violateIfNoMatchesFound: Bool = false
) throws -> [Violation] {
validate(regex: regex, matchesForEach: matchingExamples, checkInfo: checkInfo)
validate(regex: regex, doesNotMatchAny: nonMatchingExamples, checkInfo: checkInfo)
validate(regex: regex, matchesForEach: matchingExamples, check: check)
validate(regex: regex, doesNotMatchAny: nonMatchingExamples, check: check)
validateParameterCombinations(
checkInfo: checkInfo,
check: check,
autoCorrectReplacement: autoCorrectReplacement,
autoCorrectExamples: autoCorrectExamples,
violateIfNoMatchesFound: violateIfNoMatchesFound
)

if let autoCorrectReplacement = autoCorrectReplacement {
validateAutocorrectsAll(
checkInfo: checkInfo,
check: check,
examples: autoCorrectExamples,
regex: regex,
autocorrectReplacement: autoCorrectReplacement
Expand All @@ -116,9 +116,9 @@ public enum Lint {
)

let violations = try FilePathsChecker(
id: checkInfo.id,
hint: checkInfo.hint,
severity: checkInfo.severity,
id: check.id,
hint: check.hint,
severity: check.severity,
regex: regex,
filePathsToCheck: filePathsToCheck,
autoCorrectReplacement: autoCorrectReplacement,
Expand All @@ -132,8 +132,8 @@ public enum Lint {
/// Run custom scripts as checks.
///
/// - Returns: If the command produces an output in the ``LintResults`` JSON format, will forward them. If the output iis an array of ``Violation`` instances, they will be wrapped in a ``LintResults`` object. Else, it will report exactly one violation if the command has a non-zero exit code with the last line(s) of output.
public static func runCustomScript(checkInfo: CheckInfo, command: String) throws -> LintResults {
let tempScriptFileUrl = URL(fileURLWithPath: "_\(checkInfo.id).tempscript")
public static func runCustomScript(check: Check, command: String) throws -> LintResults {
let tempScriptFileUrl = URL(fileURLWithPath: "_\(check.id).tempscript")
try command.write(to: tempScriptFileUrl, atomically: true, encoding: .utf8)

let output = try shellOut(to: "/bin/bash", arguments: [tempScriptFileUrl.path])
Expand All @@ -147,30 +147,30 @@ public enum Lint {
let jsonData = jsonString.data(using: .utf8),
let violations: [Violation] = try? JSONDecoder.iso.decode([Violation].self, from: jsonData)
{
return [checkInfo.severity: [checkInfo: violations]]
return [check.severity: [check: violations]]
}
else {
return [checkInfo.severity: [checkInfo: [Violation()]]]
return [check.severity: [check: [Violation()]]]
}
}

static func validate(regex: Regex, matchesForEach matchingExamples: [String], checkInfo: CheckInfo) {
static func validate(regex: Regex, matchesForEach matchingExamples: [String], check: Check) {
for example in matchingExamples {
if !regex.matches(example) {
log.message(
"Couldn't find a match for regex \(regex) in check '\(checkInfo.id)' within matching example:\n\(example)",
"Couldn't find a match for regex \(regex) in check '\(check.id)' within matching example:\n\(example)",
level: .error
)
log.exit(fail: true)
}
}
}

static func validate(regex: Regex, doesNotMatchAny nonMatchingExamples: [String], checkInfo: CheckInfo) {
static func validate(regex: Regex, doesNotMatchAny nonMatchingExamples: [String], check: Check) {
for example in nonMatchingExamples {
if regex.matches(example) {
log.message(
"Unexpectedly found a match for regex \(regex) in check '\(checkInfo.id)' within non-matching example:\n\(example)",
"Unexpectedly found a match for regex \(regex) in check '\(check.id)' within non-matching example:\n\(example)",
level: .error
)
log.exit(fail: true)
Expand All @@ -179,7 +179,7 @@ public enum Lint {
}

static func validateAutocorrectsAll(
checkInfo: CheckInfo,
check: Check,
examples: [AutoCorrection],
regex: Regex,
autocorrectReplacement: String
Expand All @@ -189,7 +189,7 @@ public enum Lint {
if autocorrected != autocorrect.after {
log.message(
"""
Autocorrecting example for \(checkInfo.id) did not result in expected output.
Autocorrecting example for \(check.id) did not result in expected output.
Before: '\(autocorrect.before.showWhitespacesAndNewlines())'
After: '\(autocorrected.showWhitespacesAndNewlines())'
Expected: '\(autocorrect.after.showWhitespacesAndNewlines())'
Expand All @@ -202,21 +202,21 @@ public enum Lint {
}

static func validateParameterCombinations(
checkInfo: CheckInfo,
check: Check,
autoCorrectReplacement: String?,
autoCorrectExamples: [AutoCorrection],
violateIfNoMatchesFound: Bool?
) {
if autoCorrectExamples.isFilled && autoCorrectReplacement == nil {
log.message(
"`autoCorrectExamples` provided for check \(checkInfo.id) without specifying an `autoCorrectReplacement`.",
"`autoCorrectExamples` provided for check \(check.id) without specifying an `autoCorrectReplacement`.",
level: .warning
)
}

guard autoCorrectReplacement == nil || violateIfNoMatchesFound != true else {
log.message(
"Incompatible options specified for check \(checkInfo.id): `autoCorrectReplacement` and `violateIfNoMatchesFound` can't be used together.",
"Incompatible options specified for check \(check.id): `autoCorrectReplacement` and `violateIfNoMatchesFound` can't be used together.",
level: .error
)
log.exit(fail: true)
Expand Down
Loading

0 comments on commit 8b4416d

Please sign in to comment.