diff --git a/src/__tests__/filePatterns.spec.ts b/src/__tests__/filePatterns.spec.ts new file mode 100644 index 0000000..5243414 --- /dev/null +++ b/src/__tests__/filePatterns.spec.ts @@ -0,0 +1,144 @@ +import colourLog from '@Utils/colourLog' +import { Linter } from '@Types' + +import getFilePatterns from '../filePatterns' + +describe('filePatterns', () => { + + jest.spyOn(colourLog, 'config').mockImplementation(() => {}) + jest.spyOn(console, 'log').mockImplementation(() => {}) + + it('returns the correct file patterns for eslint', () => { + const filePatterns = getFilePatterns({}) + + const expectedPatterns = [ + '**/*.{cjs,js,jsx,mjs,ts,tsx}', + ] + + expect(filePatterns.includePatterns[Linter.ESLint]).toEqual(expectedPatterns) + expect(colourLog.config).toHaveBeenCalledWith('ESLint Patterns', expectedPatterns) + }) + + it('returns the correct file patterns for eslint with an additional include pattern', () => { + const filePatterns = getFilePatterns({ + eslintInclude: 'foo' + }) + + const expectedPatterns = [ + '**/*.{cjs,js,jsx,mjs,ts,tsx}', + 'foo', + ] + + expect(filePatterns.includePatterns[Linter.ESLint]).toEqual(expectedPatterns) + expect(colourLog.config).toHaveBeenCalledWith('ESLint Patterns', expectedPatterns) + }) + + it('returns the correct file patterns for eslint with several additional include patterns', () => { + const filePatterns = getFilePatterns({ + eslintInclude: ['foo', 'bar'], + }) + + const expectedPatterns = [ + '**/*.{cjs,js,jsx,mjs,ts,tsx}', + 'foo', + 'bar', + ] + + expect(filePatterns.includePatterns[Linter.ESLint]).toEqual(expectedPatterns) + expect(colourLog.config).toHaveBeenCalledWith('ESLint Patterns', expectedPatterns) + }) + + it('returns the correct file patterns for markdownlint', () => { + const filePatterns = getFilePatterns({}) + + const expectedPatterns = [ + '**/*.{md,mdx}', + ] + + expect(filePatterns.includePatterns[Linter.Markdownlint]).toEqual(expectedPatterns) + expect(colourLog.config).toHaveBeenCalledWith('Markdownlint Patterns', expectedPatterns) + }) + + it('returns the correct file patterns for stylelint', () => { + const filePatterns = getFilePatterns({}) + + const expectedPatterns = [ + '**/*.{css,scss,less,sass,styl,stylus}', + ] + + expect(filePatterns.includePatterns[Linter.Stylelint]).toEqual(expectedPatterns) + expect(colourLog.config).toHaveBeenCalledWith('Stylelint Patterns', expectedPatterns) + }) + + it('returns the correct ignore file patterns', () => { + const filePatterns = getFilePatterns({}) + + const expectedPatterns = [ + '**/+(coverage|dist|node_modules|tmp|tscOutput|vendor)/**', + '**/*.+(map|min).*', + ] + + expect(filePatterns.ignorePatterns).toEqual(expectedPatterns) + expect(colourLog.config).toHaveBeenCalledWith('Ignore', expectedPatterns) + }) + + it('returns the correct ignore file patterns with an additional directory ignored', () => { + const filePatterns = getFilePatterns({ + ignoreDirs: 'foo' + }) + + const expectedPatterns = [ + '**/+(coverage|dist|foo|node_modules|tmp|tscOutput|vendor)/**', + '**/*.+(map|min).*', + ] + + expect(filePatterns.ignorePatterns).toEqual(expectedPatterns) + expect(colourLog.config).toHaveBeenCalledWith('Ignore', expectedPatterns) + }) + + it('returns the correct ignore file patterns with several additional directories ignored', () => { + const filePatterns = getFilePatterns({ + ignoreDirs: ['foo', 'bar'], + }) + + const expectedPatterns = [ + '**/+(bar|coverage|dist|foo|node_modules|tmp|tscOutput|vendor)/**', + '**/*.+(map|min).*', + ] + + expect(filePatterns.ignorePatterns).toEqual(expectedPatterns) + expect(colourLog.config).toHaveBeenCalledWith('Ignore', expectedPatterns) + }) + + it('returns the correct ignore file patterns with an additional pattern ignored', () => { + const filePatterns = getFilePatterns({ + ignorePatterns: 'foo' + }) + + const expectedPatterns = [ + '**/+(coverage|dist|node_modules|tmp|tscOutput|vendor)/**', + '**/*.+(map|min).*', + 'foo', + ] + + expect(filePatterns.ignorePatterns).toEqual(expectedPatterns) + expect(colourLog.config).toHaveBeenCalledWith('Ignore', expectedPatterns) + }) + + it('returns the correct ignore file patterns with several additional patterns ignored', () => { + const filePatterns = getFilePatterns({ + ignorePatterns: ['foo', 'bar'], + }) + + const expectedPatterns = [ + '**/+(coverage|dist|node_modules|tmp|tscOutput|vendor)/**', + '**/*.+(map|min).*', + 'bar', + 'foo', + ] + + expect(filePatterns.ignorePatterns).toEqual(expectedPatterns) + expect(colourLog.config).toHaveBeenCalledWith('Ignore', expectedPatterns) + }) + +}) diff --git a/src/filePatterns.ts b/src/filePatterns.ts new file mode 100644 index 0000000..3ac5d7e --- /dev/null +++ b/src/filePatterns.ts @@ -0,0 +1,56 @@ +import { type FilePatterns, Linter } from '@Types' +import colourLog from '@Utils/colourLog' + +type StringOrArray = string | Array + +interface GetFilePatterns { + eslintInclude?: StringOrArray + ignoreDirs?: StringOrArray + ignorePatterns?: StringOrArray +} + +const enforceArray = (value: StringOrArray = []): Array => Array.of(value).flat() + +const getFilePatterns = ({ eslintInclude, ignoreDirs, ignorePatterns }: GetFilePatterns): FilePatterns => { + const eslintIncludePatterns = [ + '**/*.{cjs,js,jsx,mjs,ts,tsx}', + ...enforceArray(eslintInclude), + ] + + const ignoreDirectories = [ + 'coverage', + 'dist', + 'node_modules', + 'tmp', + 'tscOutput', + 'vendor', + ...enforceArray(ignoreDirs), + ].sort() + + const ignoreGlobs = [ + '**/*.+(map|min).*', + ...enforceArray(ignorePatterns), + ].sort() + + const filePatterns = { + includePatterns: { + [Linter.ESLint]: eslintIncludePatterns, + [Linter.Markdownlint]: ['**/*.{md,mdx}'], + [Linter.Stylelint]: ['**/*.{css,scss,less,sass,styl,stylus}'], + }, + ignorePatterns: [ + `**/+(${ignoreDirectories.join('|')})/**`, + ...ignoreGlobs, + ], + } + + colourLog.config('ESLint Patterns', filePatterns.includePatterns[Linter.ESLint]) + colourLog.config('Markdownlint Patterns', filePatterns.includePatterns[Linter.Markdownlint]) + colourLog.config('Stylelint Patterns', filePatterns.includePatterns[Linter.Stylelint]) + colourLog.config('Ignore', filePatterns.ignorePatterns) + console.log() + + return filePatterns +} + +export default getFilePatterns diff --git a/src/index.ts b/src/index.ts index 2ca12af..dd4a60d 100755 --- a/src/index.ts +++ b/src/index.ts @@ -8,6 +8,7 @@ import { clearTerminal } from '@Utils/terminal' import type { LintReport, RunLinter, RunLintPilot } from '@Types' +import getFilePatterns from './filePatterns' import linters from './linters/index' import sourceFiles from './sourceFiles' import { fileChangeEvent, watchFiles } from './watchFiles' @@ -21,16 +22,13 @@ program .addHelpText('beforeAll', '\n✈️ Lint Pilot ✈️\n') .showHelpAfterError('\n💡 Run `lint-pilot --help` for more information.\n') -const runLinter = async ({ filePattern, linter }: RunLinter) => { +const runLinter = async ({ filePattern, linter, ignore }: RunLinter) => { const startTime = new Date().getTime() colourLog.info(`Running ${linter.toLowerCase()}...`) const files = await sourceFiles({ filePattern, - ignore: [ - '**/*.min.*', - '**/+(coverage|node_modules)/**', - ], + ignore, linter, }) @@ -41,18 +39,21 @@ const runLinter = async ({ filePattern, linter }: RunLinter) => { return report } -const runLintPilot = ({ title, watch }: RunLintPilot) => { +const runLintPilot = ({ filePatterns, title, watch }: RunLintPilot) => { Promise.all([ runLinter({ - filePattern: '**/*.{cjs,js,jsx,mjs,ts,tsx}', + filePattern: filePatterns.includePatterns[Linter.ESLint], + ignore: filePatterns.ignorePatterns, linter: Linter.ESLint, }), runLinter({ - filePattern: '**/*.{md,mdx}', + filePattern: filePatterns.includePatterns[Linter.Markdownlint], + ignore: filePatterns.ignorePatterns, linter: Linter.Markdownlint, }), runLinter({ - filePattern: '**/*.{css,scss,less,sass,styl,stylus}', + filePattern: filePatterns.includePatterns[Linter.Stylelint], + ignore: filePatterns.ignorePatterns, linter: Linter.Stylelint, }), ]).then((reports) => { @@ -78,35 +79,39 @@ const runLintPilot = ({ title, watch }: RunLintPilot) => { program .option('-e, --emoji ', 'customise the emoji displayed when running lint-pilot', '✈️') .option('-t, --title ', 'customise the title displayed when running lint-pilot', 'Lint Pilot') + .option('-w, --watch', 'watch for file changes and re-run the linters', false) + + .option('--ignore-dirs ', 'Directories to ignore globally') + .option('--ignore-patterns ', 'File patterns to ignore globally') + .option('--eslint-include ', 'File patterns to include for ESLint') + .option('--debug', 'output additional debug information including the list of files being linted', false) - .action(({ debug, emoji, title, watch }) => { + .action(({ debug, emoji, eslintInclude, ignoreDirs, ignorePatterns, title, watch }) => { clearTerminal() colourLog.title(`${emoji} ${title} ${emoji}`) console.log() global.debug = debug - runLintPilot({ title, watch }) + const filePatterns = getFilePatterns({ + eslintInclude, + ignoreDirs, + ignorePatterns, + }) + runLintPilot({ filePatterns, title, watch }) if (watch) { watchFiles({ - filePatterns: [ - '**/*.{cjs,js,jsx,mjs,ts,tsx}', - '**/*.{md,mdx}', - '**/*.{css,scss,less,sass,styl,stylus}', - ], - ignorePatterns: [ - '**/*.min.*', - '**/+(coverage|node_modules)/**', - ], + filePatterns: Object.values(filePatterns.includePatterns).flat(), + ignorePatterns: filePatterns.ignorePatterns, }) fileChangeEvent.on(Events.FILE_CHANGED, ({ message }) => { clearTerminal() colourLog.info(message) console.log() - runLintPilot({ title, watch }) + runLintPilot({ filePatterns, title, watch }) }) } }) diff --git a/src/sourceFiles.ts b/src/sourceFiles.ts index 3202b23..f45e475 100644 --- a/src/sourceFiles.ts +++ b/src/sourceFiles.ts @@ -5,8 +5,8 @@ import colourLog from '@Utils/colourLog' import { pluralise } from '@Utils/transform' interface SourceFiles { - filePattern: string - ignore: Array | string + filePattern: Array + ignore: Array linter: Linter } diff --git a/src/types/index.ts b/src/types/index.ts index af81036..1787a70 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -54,12 +54,21 @@ interface LintReport { * LINT PILOT */ +interface FilePatterns { + includePatterns: { + [key in Linter]: Array + } + ignorePatterns: Array +} + interface RunLinter { - filePattern: string + filePattern: Array + ignore: Array linter: Linter } interface RunLintPilot { + filePatterns: FilePatterns title: string watch: boolean } @@ -69,6 +78,7 @@ interface RunLintPilot { */ export type { + FilePatterns, FormattedResult, LintReport, ReportResults,