From 8c24d304860f501c9d9d4ccf8b9f7553819f3d2b Mon Sep 17 00:00:00 2001 From: Christopher Allford <6451942+ObliviousHarmony@users.noreply.github.com> Date: Fri, 29 Jul 2022 16:31:31 -0700 Subject: [PATCH] Use Glob Patterns For Exclusions Instead Of Regular Expressions (#51) This adds a new `phpCodeSniffer.exclude` option that replaces the `phpCodeSniffer.ignorePatterns` option. The new option uses glob patterns instead of regular expressions, making it consistent with other extensions. --- CHANGELOG.md | 6 + README.md | 6 +- package-lock.json | 180 ++++++++---------- package.json | 16 ++ src/providers/format-document-provider.ts | 5 +- src/services/__tests__/configuration.spec.ts | 78 +++++++- .../__tests__/diagnostic-updater.spec.ts | 10 +- src/services/configuration.ts | 35 ++-- src/services/diagnostic-updater.ts | 11 +- 9 files changed, 211 insertions(+), 136 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eb70034..393061b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,10 +5,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added +- Use glob patterns to exclude files and folders from linting using the `phpCodeSniffer.exclude` option. + ### Fixed - Document formatting with no changes clears diagnostics. - Document selection formatting only works on the first character of the diagnostic. +### Deprecated +- `phpCodeSniffer.ignorePatterns` has been deprecated in favor of using glob patterns over regular expressions. + ## [1.6.0] - 2022-05-06 ### Fixed - `phpCodeSniffer.executable` options with spaces throwing errors. diff --git a/README.md b/README.md index 8035ba0..ba692bc 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ VS Code's available features. ## Configuration -Out of the box this extension will do nothing and must be configured prior to use. +_**Until you configure it, this extension will not lint any files.**_ ### Standard (`phpCodeSniffer.standard`) @@ -25,3 +25,7 @@ the `phpCodeSniffer.autoExecutable` option if you'd like for the extension to au executable. This works by looking for a `{vendor-dir}/bin/phpcs` file in the document's directory and then traversing up to the workspace folder if it does not find one. When it fails to find one automatically it will fall back to the explicit option. + +### File and Folder Exclusions (`phpCodeSniffer.exclude`) + +This array of glob patterns allows you to exclude files and folders from linting. While the extension **does** respect any file rules in your coding standard, this option allows you to define additional rules. diff --git a/package-lock.json b/package-lock.json index 3d35adf..a082c8b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,8 +8,12 @@ "name": "vscode-php-codesniffer", "version": "1.6.0", "license": "GPL-2.0-or-later", + "dependencies": { + "minimatch": "^3.0.4" + }, "devDependencies": { "@types/jest": "^26.0.23", + "@types/minimatch": "^3.0.4", "@types/node": "^12.20.37", "@types/vscode": "~1.53.0", "@typescript-eslint/eslint-plugin": "^4.33.0", @@ -984,6 +988,30 @@ "node": ">=6.0.0" } }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", + "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "node_modules/@jridgewell/source-map/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.4.13", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.13.tgz", @@ -1178,6 +1206,12 @@ "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", "dev": true }, + "node_modules/@types/minimatch": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", + "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==", + "dev": true + }, "node_modules/@types/node": { "version": "12.20.50", "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.50.tgz", @@ -1966,8 +2000,7 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "node_modules/base": { "version": "0.11.2", @@ -2063,7 +2096,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2565,8 +2597,7 @@ "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "node_modules/console-control-strings": { "version": "1.1.0", @@ -5494,12 +5525,6 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, - "node_modules/lodash.sortby": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", - "dev": true - }, "node_modules/lodash.truncate": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", @@ -5714,7 +5739,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -8020,14 +8044,14 @@ } }, "node_modules/terser": { - "version": "5.13.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.13.1.tgz", - "integrity": "sha512-hn4WKOfwnwbYfe48NgrQjqNOH9jzLqRcIfbYytOXCOv46LBfWr9bDS17MQqOi+BWGD0sJK3Sj5NC/gJjiojaoA==", + "version": "5.14.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz", + "integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==", "dev": true, "dependencies": { + "@jridgewell/source-map": "^0.3.2", "acorn": "^8.5.0", "commander": "^2.20.0", - "source-map": "~0.8.0-beta.0", "source-map-support": "~0.5.20" }, "bin": { @@ -8118,44 +8142,6 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true }, - "node_modules/terser/node_modules/source-map": { - "version": "0.8.0-beta.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", - "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", - "dev": true, - "dependencies": { - "whatwg-url": "^7.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/terser/node_modules/tr46": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", - "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", - "dev": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/terser/node_modules/webidl-conversions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", - "dev": true - }, - "node_modules/terser/node_modules/whatwg-url": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", - "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", - "dev": true, - "dependencies": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" - } - }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -9996,6 +9982,29 @@ "integrity": "sha512-Ct5MqZkLGEXTVmQYbGtx9SVqD2fqwvdubdps5D3djjAkgkKwT918VNOz65pEHFaYTeWcukmJmH5SwsA9Tn2ObQ==", "dev": true }, + "@jridgewell/source-map": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", + "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "dependencies": { + "@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + } + } + }, "@jridgewell/sourcemap-codec": { "version": "1.4.13", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.13.tgz", @@ -10178,6 +10187,12 @@ "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", "dev": true }, + "@types/minimatch": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", + "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==", + "dev": true + }, "@types/node": { "version": "12.20.50", "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.50.tgz", @@ -10782,8 +10797,7 @@ "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "base": { "version": "0.11.2", @@ -10857,7 +10871,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -11236,8 +11249,7 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "console-control-strings": { "version": "1.1.0", @@ -13472,12 +13484,6 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, - "lodash.sortby": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", - "dev": true - }, "lodash.truncate": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", @@ -13642,7 +13648,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "requires": { "brace-expansion": "^1.1.7" } @@ -15432,14 +15437,14 @@ } }, "terser": { - "version": "5.13.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.13.1.tgz", - "integrity": "sha512-hn4WKOfwnwbYfe48NgrQjqNOH9jzLqRcIfbYytOXCOv46LBfWr9bDS17MQqOi+BWGD0sJK3Sj5NC/gJjiojaoA==", + "version": "5.14.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz", + "integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==", "dev": true, "requires": { + "@jridgewell/source-map": "^0.3.2", "acorn": "^8.5.0", "commander": "^2.20.0", - "source-map": "~0.8.0-beta.0", "source-map-support": "~0.5.20" }, "dependencies": { @@ -15454,41 +15459,6 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true - }, - "source-map": { - "version": "0.8.0-beta.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", - "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", - "dev": true, - "requires": { - "whatwg-url": "^7.0.0" - } - }, - "tr46": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", - "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "webidl-conversions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", - "dev": true - }, - "whatwg-url": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", - "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", - "dev": true, - "requires": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" - } } } }, diff --git a/package.json b/package.json index cd31aae..ba5a5a2 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "phpCodeSniffer.ignorePatterns": { "type": "array", "description": "An array of regular expressions for paths that should be ignored by the extension.", + "markdownDeprecationMessage": "File and folder exclusions should use glob patterns in `#phpCodeSniffer.exclude#` instead.", "default": [ ".*/vendor/.*" ], @@ -56,6 +57,17 @@ }, "scope": "window" }, + "phpCodeSniffer.exclude": { + "type": "array", + "description": "Glob patterns for files and folders that should be ignored by the extension.", + "default": [ + "**/vendor/**" + ], + "items": { + "type": "string" + }, + "scope": "window" + }, "phpCodeSniffer.lintAction": { "type": "string", "description": "The editor action that will cause the linter to run.", @@ -111,10 +123,14 @@ } ] }, + "dependencies": { + "minimatch": "^3.0.4" + }, "devDependencies": { "@types/jest": "^26.0.23", "@types/node": "^12.20.37", "@types/vscode": "~1.53.0", + "@types/minimatch": "^3.0.4", "@typescript-eslint/eslint-plugin": "^4.33.0", "@typescript-eslint/parser": "^4.33.0", "eslint": "^7.27.0", diff --git a/src/providers/format-document-provider.ts b/src/providers/format-document-provider.ts index 22d7583..68b0c91 100644 --- a/src/providers/format-document-provider.ts +++ b/src/providers/format-document-provider.ts @@ -16,8 +16,9 @@ import { DocumentFormatter } from '../services/document-formatter'; */ export class FormatDocumentProvider implements - DocumentFormattingEditProvider, - DocumentRangeFormattingEditProvider { + DocumentFormattingEditProvider, + DocumentRangeFormattingEditProvider +{ /** * The formatter that we will use. */ diff --git a/src/services/__tests__/configuration.spec.ts b/src/services/__tests__/configuration.spec.ts index cbcb993..89fee47 100644 --- a/src/services/__tests__/configuration.spec.ts +++ b/src/services/__tests__/configuration.spec.ts @@ -69,12 +69,16 @@ describe('Configuration', () => { return false; case 'executable': return 'test.exec'; - case 'ignorePatterns': - return ['test']; + case 'exclude': + return ['test/{test|test-test}/*.php']; case 'lintAction': return LintAction.Change; case 'standard': return StandardType.Disabled; + + // Deprecated options. + case 'ignorePatterns': + return undefined; } fail( @@ -91,7 +95,7 @@ describe('Configuration', () => { expect(result).toMatchObject({ workingDirectory: 'test/file', executable: 'test.exec', - ignorePatterns: [new RegExp('test')], + exclude: [/^(?:test\/\\{test|test-test}\/(?!\.)(?=.)[^/]*?\.php)$/], standard: StandardType.Disabled, }); @@ -153,12 +157,16 @@ describe('Configuration', () => { return true; case 'executable': return 'test.exec'; - case 'ignorePatterns': - return ['test']; + case 'exclude': + return []; case 'lintAction': return LintAction.Change; case 'standard': return StandardType.Disabled; + + // Deprecated settings. + case 'ignorePatterns': + return undefined; } fail( @@ -175,7 +183,7 @@ describe('Configuration', () => { expect(result).toMatchObject({ workingDirectory: 'test', executable: 'test/newvendor/bin/phpcs', - ignorePatterns: [new RegExp('test')], + exclude: [], standard: StandardType.Disabled, }); @@ -239,12 +247,16 @@ describe('Configuration', () => { return true; case 'executable': return 'test.exec'; - case 'ignorePatterns': - return ['test']; + case 'exclude': + return ['test/{test|test-test}/*.php']; case 'lintAction': return LintAction.Change; case 'standard': return StandardType.Disabled; + + // Deprecated settings. + case 'ignorePatterns': + return undefined; } fail( @@ -259,4 +271,54 @@ describe('Configuration', () => { return expect(promise).rejects.toMatchObject(new CancellationError()); }); + + describe('deprecated options', () => { + it('should handle "ignorePatterns" deprecation', async () => { + const mockConfiguration = { get: jest.fn() }; + mocked(workspace).getConfiguration.mockReturnValue( + mockConfiguration as never + ); + + mockConfiguration.get.mockImplementation((key) => { + switch (key) { + case 'autoExecutable': + return false; + case 'executable': + return 'test.exec'; + case 'exclude': + return ['test/{test|test-test}/*.php']; + case 'lintAction': + return LintAction.Change; + case 'standard': + return StandardType.Disabled; + + // Deprecated options. + case 'ignorePatterns': + return ['test']; + } + + fail( + 'An unexpected configuration key of ' + + key + + ' was received.' + ); + }); + + const result = await configuration.get(mockDocument); + + expect(workspace.getConfiguration).toHaveBeenCalledWith( + 'phpCodeSniffer', + mockDocument + ); + expect(result).toMatchObject({ + workingDirectory: 'test', + executable: 'test.exec', + exclude: [ + /^(?:test\/\\{test|test-test}\/(?!\.)(?=.)[^/]*?\.php)$/, + /test/, + ], + standard: StandardType.Disabled, + }); + }); + }); }); diff --git a/src/services/__tests__/diagnostic-updater.spec.ts b/src/services/__tests__/diagnostic-updater.spec.ts index 4fc3a3f..e333fdb 100644 --- a/src/services/__tests__/diagnostic-updater.spec.ts +++ b/src/services/__tests__/diagnostic-updater.spec.ts @@ -95,7 +95,7 @@ describe('DiagnosticUpdater', () => { mocked(mockConfiguration).get.mockResolvedValue({ workingDirectory: 'test-dir', executable: 'phpcs-test', - ignorePatterns: [], + exclude: [], lintAction: LintAction.Change, standard: StandardType.PSR12, }); @@ -152,7 +152,7 @@ describe('DiagnosticUpdater', () => { mocked(mockConfiguration).get.mockResolvedValue({ workingDirectory: 'test-dir', executable: 'phpcs-test', - ignorePatterns: [], + exclude: [], lintAction: LintAction.Change, standard: StandardType.PSR12, }); @@ -178,14 +178,14 @@ describe('DiagnosticUpdater', () => { diagnosticUpdater.update(document, LintAction.Force); }); - it('should respect ignore patterns', () => { + it('should respect exclude patterns', () => { const document = new MockTextDocument(); document.fileName = 'test-document'; mocked(mockConfiguration).get.mockResolvedValue({ workingDirectory: 'test-dir', executable: 'phpcs-test', - ignorePatterns: [new RegExp('.*/file/.*')], + exclude: [new RegExp('.*/file/.*')], lintAction: LintAction.Change, standard: StandardType.PSR12, }); @@ -200,7 +200,7 @@ describe('DiagnosticUpdater', () => { mocked(mockConfiguration).get.mockResolvedValue({ workingDirectory: 'test-dir', executable: 'phpcs-test', - ignorePatterns: [], + exclude: [], lintAction: LintAction.Save, standard: StandardType.PSR12, }); diff --git a/src/services/configuration.ts b/src/services/configuration.ts index 15501b6..104131c 100644 --- a/src/services/configuration.ts +++ b/src/services/configuration.ts @@ -1,4 +1,5 @@ import { TextDecoder } from 'util'; +import { Minimatch } from 'minimatch'; import { CancellationError, CancellationToken, @@ -56,7 +57,7 @@ interface ParamsFromFilesystem { interface ParamsFromConfiguration { autoExecutable: boolean; executable: string; - ignorePatterns: RegExp[]; + exclude: RegExp[]; lintAction: LintAction; standard: string; } @@ -76,9 +77,9 @@ export interface DocumentConfiguration { executable: string; /** - * The ignore patterns we should use when executing reports/ + * The patterns we should use when excluding files and folders from reports. */ - ignorePatterns: RegExp[]; + exclude: RegExp[]; /** * The editor action that should trigger the linter. @@ -148,7 +149,7 @@ export class Configuration { config = { workingDirectory: fromFilesystem.workingDirectory, executable: fromFilesystem.executable ?? fromConfig.executable, - ignorePatterns: fromConfig.ignorePatterns, + exclude: fromConfig.exclude, lintAction: fromConfig.lintAction, standard: fromConfig.standard, }; @@ -226,29 +227,41 @@ export class Configuration { const autoExecutable = config.get('autoExecutable'); if (autoExecutable === undefined) { throw new Error( - 'The extension has an invalid `autoExecutable` configuration.' + 'The extension has an invalid `phpCodeSniffer.autoExecutable` configuration.' ); } const executable = config.get('executable'); if (executable === undefined) { throw new Error( - 'The extension has an invalid `executable` configuration.' + 'The extension has an invalid `phpCodeSniffer.executable` configuration.' ); } - const rawPatterns = config.get('ignorePatterns'); + let rawPatterns = config.get('exclude'); if (!Array.isArray(rawPatterns)) { throw new Error( - 'The extension has an invalid `ignorePatterns` configuration.' + 'The extension has an invalid `phpCodeSniffer.exclude` configuration.' ); } - const ignorePatterns = rawPatterns.map((v) => new RegExp(v)); + const exclude = rawPatterns.map((v) => new Minimatch(v).makeRe()); + + // Support the deprecated `ignorePatterns` option. + rawPatterns = config.get('ignorePatterns'); + if (rawPatterns) { + if (!Array.isArray(rawPatterns)) { + throw new Error( + 'The extension has an invalid `phpCodeSniffer.ignorePatterns` configuration.' + ); + } + + exclude.push(...rawPatterns.map((v) => new RegExp(v))); + } const lintAction = config.get('lintAction'); if (lintAction === undefined) { throw new Error( - 'The extension has an invalid `lintAction` configuration.' + 'The extension has an invalid `phpCodeSniffer.lintAction` configuration.' ); } @@ -263,7 +276,7 @@ export class Configuration { return { autoExecutable, executable, - ignorePatterns, + exclude, lintAction, standard, }; diff --git a/src/services/diagnostic-updater.ts b/src/services/diagnostic-updater.ts index daa7f29..4843b26 100644 --- a/src/services/diagnostic-updater.ts +++ b/src/services/diagnostic-updater.ts @@ -108,10 +108,14 @@ export class DiagnosticUpdater extends WorkerService { this.configuration .get(document, cancellationToken) .then((configuration) => { - // Check the file's path against our ignore patterns so that we don't process + // Check the file's path against our exclude patterns so that we don't process // diagnostics for files that the user is not interested in receiving them for. - for (const pattern of configuration.ignorePatterns) { + for (const pattern of configuration.exclude) { if (pattern.test(document.uri.fsPath)) { + // When an open file wasn't excluded at first but becomes excluded, it will leave + // behind diagnostics and code actions. To avoid this, let's always clear the + // data for documents that are excluded from diagnostic processing. + this.clearDocument(document); throw new UpdatePreventedError(); } } @@ -165,8 +169,7 @@ export class DiagnosticUpdater extends WorkerService { // When an empty response is received it means that there are no diagnostics for the file. if (!response.report) { - this.diagnosticCollection.delete(document.uri); - this.codeActionCollection.delete(document.uri); + this.clearDocument(document); return; }