diff --git a/bin/checksync.dev.js b/bin/checksync.dev.js index 5a65cd3c0..a72500f2d 100755 --- a/bin/checksync.dev.js +++ b/bin/checksync.dev.js @@ -1,3 +1,3 @@ #!/usr/bin/env node require("@babel/register"); -require("../src/cli.js").run(__filename); +require("../src/main.js").run(__filename); diff --git a/bin/checksync.js b/bin/checksync.js index 9bcd812cf..9bb4de3dc 100755 --- a/bin/checksync.js +++ b/bin/checksync.js @@ -1,2 +1,2 @@ #!/usr/bin/env node -require("../dist/cli.js").run(__filename); +require("../dist/main.js").run(__filename); diff --git a/package.json b/package.json index 6a3c7f1e0..7edef9874 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "checksync", "version": "2.3.0", "description": "A tool that allows code to be annotated across different files to ensure they remain in sync.", - "main": "dist/cli.js", + "main": "dist/main.js", "bin": { "checksync": "./bin/checksync.js" }, diff --git a/rollup.config.js b/rollup.config.js index 6d891e3d3..d93270400 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -26,9 +26,9 @@ const getOptionalPlugins = () => { }; export default { - input: "./src/cli.js", + input: "./src/main.js", output: { - file: "./dist/cli.js", + file: "./dist/main.js", format: "cjs", }, plugins: [ diff --git a/src/__tests__/__snapshots__/cli_test.js.snap b/src/__tests__/__snapshots__/cli_test.js.snap index d71fc9c2c..53e6b4ff7 100644 --- a/src/__tests__/__snapshots__/cli_test.js.snap +++ b/src/__tests__/__snapshots__/cli_test.js.snap @@ -47,15 +47,15 @@ Where: Arguments - --comments,-c A string containing comma-separated tokens that + --comments,-c A string containing space-separated tokens that indicate the start of lines where tags appear. - Defaults to \\"//,#\\". + Defaults to \\"// #\\". --dry-run,-n Ignored unless supplied with --update-tags. --help,-h Outputs this help text. - --ignore,-i A string containing comma-separated globs that identify + --ignore,-i A string containing semi-colon-separated globs that identify files that should not be checked. --ignore-files A comma-separated list of .gitignore-like files that diff --git a/src/__tests__/__snapshots__/get-files_test.js.snap b/src/__tests__/__snapshots__/get-files_test.js.snap deleted file mode 100644 index ae6a4b562..000000000 --- a/src/__tests__/__snapshots__/get-files_test.js.snap +++ /dev/null @@ -1,17 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`#getFiles should log matching snapshot 1`] = ` -" VERBOSE Include globs: [] - VERBOSE Exclude globs: [ - \\"**/a/**\\", - \\"a\\", - \\"**/c/**\\", - \\"c\\" -] - VERBOSE Discovered paths: [ - \\"a\\", - \\"b\\", - \\"c\\", - \\"d\\" -]" -`; diff --git a/src/__tests__/check-sync_test.js b/src/__tests__/check-sync_test.js index 5f8555238..4e48d9e26 100644 --- a/src/__tests__/check-sync_test.js +++ b/src/__tests__/check-sync_test.js @@ -26,6 +26,7 @@ describe("#checkSync", () => { { includeGlobs: ["glob1", "glob2"], excludeGlobs: [], + ignoreFiles: [], dryRun: true, autoFix: true, comments: ["//"], @@ -50,6 +51,7 @@ describe("#checkSync", () => { { includeGlobs: ["glob1", "glob2"], excludeGlobs: [], + ignoreFiles: [], dryRun: false, autoFix: true, comments: ["//"], @@ -62,6 +64,7 @@ describe("#checkSync", () => { expect(getFilesSpy).toHaveBeenCalledWith( ["glob1", "glob2"], [], + [], NullLogger, ); }); @@ -74,6 +77,7 @@ describe("#checkSync", () => { const options: Options = { includeGlobs: ["glob1", "glob2"], excludeGlobs: [], + ignoreFiles: [], dryRun: false, autoFix: false, comments: ["//"], @@ -94,6 +98,7 @@ describe("#checkSync", () => { const options: Options = { includeGlobs: ["glob1", "glob2"], excludeGlobs: [], + ignoreFiles: [], dryRun: false, autoFix: false, comments: ["//"], @@ -118,6 +123,7 @@ describe("#checkSync", () => { const options: Options = { includeGlobs: ["glob1", "glob2"], excludeGlobs: [], + ignoreFiles: [], dryRun: false, autoFix: true, comments: ["//"], @@ -146,6 +152,7 @@ describe("#checkSync", () => { const options: Options = { includeGlobs: ["glob1", "glob2"], excludeGlobs: [], + ignoreFiles: [], dryRun: false, autoFix: false, comments: ["//"], @@ -177,6 +184,7 @@ describe("#checkSync", () => { { includeGlobs: [], excludeGlobs: [], + ignoreFiles: [], autoFix: false, comments: ["//"], dryRun: false, diff --git a/src/__tests__/cli_test.js b/src/__tests__/cli_test.js index b026a99f5..4653bc9a9 100644 --- a/src/__tests__/cli_test.js +++ b/src/__tests__/cli_test.js @@ -124,15 +124,15 @@ describe("#run", () => { const fakeParsedArgs = { ...defaultArgs, updateTags: true, - comments: "COMMENT1,COMMENT2", + comments: "COMMENT1 COMMENT2", ignoreFiles: "madeupfile", + ignore: "glob1;glob2;", _: ["globs", "and globs"], }; const checkSyncSpy = jest .spyOn(CheckSync, "default") .mockReturnValue({then: jest.fn()}); jest.spyOn(minimist, "default").mockReturnValue(fakeParsedArgs); - jest.spyOn(ParseGitIgnore, "default").mockReturnValue(["madeupglob"]); // Act run(__filename); @@ -141,7 +141,8 @@ describe("#run", () => { expect(checkSyncSpy).toHaveBeenCalledWith( { includeGlobs: fakeParsedArgs._, - excludeGlobs: ["madeupglob"], + excludeGlobs: ["glob1", "glob2"], + ignoreFiles: ["madeupfile"], dryRun: false, autoFix: true, comments: ["COMMENT1", "COMMENT2"], @@ -157,7 +158,7 @@ describe("#run", () => { const fakeParsedArgs = { ...defaultArgs, updateTags: true, - comments: "COMMENT1,COMMENT2", + comments: "COMMENT1 COMMENT2", _: ["globs", "and globs"], }; const checkSyncSpy = jest @@ -174,6 +175,7 @@ describe("#run", () => { { includeGlobs: fakeParsedArgs._, excludeGlobs: [], + ignoreFiles: [".gitignore"], dryRun: false, autoFix: true, comments: ["COMMENT1", "COMMENT2"], @@ -191,7 +193,7 @@ describe("#run", () => { updateTags: true, ignoreFile: "something", noIgnoreFile: true, - comments: "COMMENT1,COMMENT2", + comments: "COMMENT1 COMMENT2", _: ["globs", "and globs"], }; const checkSyncSpy = jest @@ -208,46 +210,7 @@ describe("#run", () => { { includeGlobs: fakeParsedArgs._, excludeGlobs: [], - dryRun: false, - autoFix: true, - comments: ["COMMENT1", "COMMENT2"], - json: false, - rootMarker: undefined, - }, - expect.any(Object), - ); - }); - - it("should combine exclude rules from given ignore files", () => { - // Arrange - const fakeParsedArgs = { - ...defaultArgs, - updateTags: true, - ignoreFiles: "IGNOREFILEA,IGNOREFILEB", - comments: "COMMENT1,COMMENT2", - _: ["globs", "and globs"], - }; - const checkSyncSpy = jest - .spyOn(CheckSync, "default") - .mockReturnValue({then: jest.fn()}); - jest.spyOn(minimist, "default").mockReturnValue(fakeParsedArgs); - jest.spyOn(ParseGitIgnore, "default").mockReturnValueOnce([ - "IGNORE1", - "IGNORE2", - ]); - jest.spyOn(ParseGitIgnore, "default").mockReturnValueOnce([ - "IGNORE1", - "IGNORE3", - ]); - - // Act - run(__filename); - - // Assert - expect(checkSyncSpy).toHaveBeenCalledWith( - { - includeGlobs: fakeParsedArgs._, - excludeGlobs: ["IGNORE1", "IGNORE2", "IGNORE1", "IGNORE3"], + ignoreFiles: [], dryRun: false, autoFix: true, comments: ["COMMENT1", "COMMENT2"], diff --git a/src/__tests__/fix-file_test.js b/src/__tests__/fix-file_test.js index 559d7d412..63648ad64 100644 --- a/src/__tests__/fix-file_test.js +++ b/src/__tests__/fix-file_test.js @@ -29,6 +29,7 @@ describe("#fixFile", () => { const options: Options = { includeGlobs: [], excludeGlobs: [], + ignoreFiles: [], dryRun: false, autoFix: true, comments: [], diff --git a/src/__tests__/get-files_test.js b/src/__tests__/get-files_test.js index 0ee2bb99e..74d132a64 100644 --- a/src/__tests__/get-files_test.js +++ b/src/__tests__/get-files_test.js @@ -2,75 +2,15 @@ import * as FastGlob from "fast-glob"; import Logger from "../logger.js"; import StringLogger from "../string-logger.js"; +import * as IgnoreFilesToExcludeGlobs from "../ignore-files-to-exclude-globs.js"; import getFiles from "../get-files.js"; +import {jest} from "@jest/globals"; jest.mock("fast-glob"); jest.mock("fs"); describe("#getFiles", () => { - it("should expand foo format includes", async () => { - // Arrange - const NullLogger = new Logger(null); - const globSpy = jest - .spyOn(FastGlob, "default") - .mockImplementation((pattern, opts) => Promise.resolve([])); - - // Act - await getFiles(["foo"], [], NullLogger); - - // Assert - expect(globSpy).toHaveBeenCalledWith( - ["**/foo/**", "foo"], - expect.any(Object), - ); - }); - - it("should expand /foo format includes", async () => { - // Arrange - const NullLogger = new Logger(null); - const globSpy = jest - .spyOn(FastGlob, "default") - .mockImplementation((pattern, opts) => Promise.resolve([])); - - // Act - await getFiles(["/foo"], [], NullLogger); - - // Assert - expect(globSpy).toHaveBeenCalledWith( - ["/foo/**", "/foo"], - expect.any(Object), - ); - }); - - it("should expand /foo/ format includes", async () => { - // Arrange - const NullLogger = new Logger(null); - const globSpy = jest - .spyOn(FastGlob, "default") - .mockImplementation((pattern, opts) => Promise.resolve([])); - - // Act - await getFiles(["/foo/"], [], NullLogger); - - // Assert - expect(globSpy).toHaveBeenCalledWith(["/foo/**"], expect.any(Object)); - }); - - it("should expand foo/ format includes", async () => { - // Arrange - const NullLogger = new Logger(null); - const globSpy = jest - .spyOn(FastGlob, "default") - .mockImplementation((pattern, opts) => Promise.resolve([])); - - // Act - await getFiles(["foo/"], [], NullLogger); - - // Assert - expect(globSpy).toHaveBeenCalledWith(["**/foo/**"], expect.any(Object)); - }); - it("should return a sorted list without duplicates", async () => { // Arrange const NullLogger = new Logger(null); @@ -81,14 +21,19 @@ describe("#getFiles", () => { ); // Act - const result = await getFiles(["pattern1", "pattern2"], [], NullLogger); + const result = await getFiles( + ["pattern1", "pattern2"], + [], + [], + NullLogger, + ); // Assert expect(result).toEqual(["a", "b", "c", "d"]); expect(globSpy).toHaveBeenCalledTimes(1); }); - it("should exclude files matched by exclude globs", async () => { + it("should exclude files matched by exclude globs and ignore files", async () => { // Arrange const NullLogger = new Logger(null); const globSpy = jest @@ -96,14 +41,17 @@ describe("#getFiles", () => { .mockImplementation((pattern, opts) => Promise.resolve(["c", "a", "d", "b"]), ); + jest.spyOn(IgnoreFilesToExcludeGlobs, "default").mockReturnValue([ + "**/ignore-file/**", + ]); // Act - await getFiles([], ["a", "c"], NullLogger); + await getFiles([], ["a", "c"], ["ignore-file"], NullLogger); // Assert expect(globSpy).toHaveBeenCalledWith( [], - expect.objectContaining({ignore: ["**/a/**", "a", "**/c/**", "c"]}), + expect.objectContaining({ignore: ["a", "c", "**/ignore-file/**"]}), ); }); @@ -116,7 +64,7 @@ describe("#getFiles", () => { const verboseSpy = jest.spyOn(NullLogger, "verbose"); // Act - await getFiles([], ["a", "c"], NullLogger); + await getFiles([], ["a", "c"], [], NullLogger); // Assert expect(verboseSpy).toHaveBeenCalledTimes(3); @@ -130,10 +78,25 @@ describe("#getFiles", () => { ); // Act - await getFiles([], ["a", "c"], logger); + await getFiles(["b", "d"], ["a", "c"], [], logger); const log = logger.getLog(); // Assert - expect(log).toMatchSnapshot(); + expect(log).toMatchInlineSnapshot(` + " VERBOSE Include globs: [ + \\"b\\", + \\"d\\" + ] + VERBOSE Exclude globs: [ + \\"a\\", + \\"c\\" + ] + VERBOSE Discovered paths: [ + \\"a\\", + \\"b\\", + \\"c\\", + \\"d\\" + ]" + `); }); }); diff --git a/src/__tests__/get-markers-from-files_test.js b/src/__tests__/get-markers-from-files_test.js index 6a85b08c5..d3d38a1b0 100644 --- a/src/__tests__/get-markers-from-files_test.js +++ b/src/__tests__/get-markers-from-files_test.js @@ -23,6 +23,7 @@ describe("#fromFiles", () => { rootMarker: null, dryRun: false, excludeGlobs: [], + ignoreFiles: [], json: false, }; @@ -58,6 +59,7 @@ describe("#fromFiles", () => { rootMarker: null, dryRun: false, excludeGlobs: [], + ignoreFiles: [], json: false, }; @@ -89,6 +91,7 @@ describe("#fromFiles", () => { rootMarker: null, dryRun: false, excludeGlobs: [], + ignoreFiles: [], json: false, }; @@ -123,6 +126,7 @@ describe("#fromFiles", () => { rootMarker: null, dryRun: false, excludeGlobs: [], + ignoreFiles: [], json: false, }; @@ -178,6 +182,7 @@ describe("#fromFiles", () => { rootMarker: null, dryRun: false, excludeGlobs: [], + ignoreFiles: [], json: false, }; @@ -232,6 +237,7 @@ describe("#fromFiles", () => { rootMarker: null, dryRun: false, excludeGlobs: [], + ignoreFiles: [], json: false, }; @@ -283,6 +289,7 @@ describe("#fromFiles", () => { rootMarker: null, dryRun: false, excludeGlobs: [], + ignoreFiles: [], json: false, }; @@ -328,6 +335,7 @@ describe("#fromFiles", () => { rootMarker: null, dryRun: false, excludeGlobs: [], + ignoreFiles: [], json: false, }; diff --git a/src/__tests__/ignore-files-to-exclude-globs_test.js b/src/__tests__/ignore-files-to-exclude-globs_test.js new file mode 100644 index 000000000..a24dc19d3 --- /dev/null +++ b/src/__tests__/ignore-files-to-exclude-globs_test.js @@ -0,0 +1,43 @@ +// @flow +import * as FS from "fs"; +import * as ParseGitIgnore from "parse-gitignore"; +import defaultArgs from "../default-args.js"; +import ignoreFilesToExcludeGlobs from "../ignore-files-to-exclude-globs.js"; + +jest.mock("fs"); +jest.mock("parse-gitignore"); + +describe("#ignoreFilesToExcludeGlobs", () => { + it("should return an empty array if the ignore files only include the defaults", () => { + // Arrange + jest.spyOn(FS, "existsSync").mockReturnValueOnce(false); + + // Act + const result = ignoreFilesToExcludeGlobs( + defaultArgs.ignoreFiles.split(","), + ); + + // Assert + expect(result).toBeEmpty(); + }); + + it("should create exclude globs from given ignore files", () => { + // Arrange + jest.spyOn(ParseGitIgnore, "default") + .mockReturnValueOnce(["IGNORE1", "IGNORE2"]) + .mockReturnValueOnce(["IGNORE1", "IGNORE3"]); + + // Act + const result = ignoreFilesToExcludeGlobs(["ignore12", "ignore13"]); + + // Assert + expect(result).toStrictEqual([ + "**/IGNORE1/**", + "IGNORE1", + "**/IGNORE2/**", + "IGNORE2", + "**/IGNORE3/**", + "IGNORE3", + ]); + }); +}); diff --git a/src/__tests__/ignore-format-to-globs_test.js b/src/__tests__/ignore-format-to-globs_test.js new file mode 100644 index 000000000..6e37a53b5 --- /dev/null +++ b/src/__tests__/ignore-format-to-globs_test.js @@ -0,0 +1,44 @@ +// @flow +import ignoreFormatToGlobs from "../ignore-format-to-globs.js"; + +describe("#ignoreFormatToGlobs", () => { + it("should expand foo format includes", () => { + // Arrange + + // Act + const result = Array.from(ignoreFormatToGlobs(["foo"])); + + // Assert + expect(result).toStrictEqual(["**/foo/**", "foo"]); + }); + + it("should expand /foo format includes", () => { + // Arrange + + // Act + const result = Array.from(ignoreFormatToGlobs(["/foo"])); + + // Assert + expect(result).toStrictEqual(["/foo/**", "/foo"]); + }); + + it("should expand /foo/ format includes", () => { + // Arrange + + // Act + const result = Array.from(ignoreFormatToGlobs(["/foo/"])); + + // Assert + expect(result).toStrictEqual(["/foo/**"]); + }); + + it("should expand foo/ format includes", () => { + // Arrange + + // Act + const result = Array.from(ignoreFormatToGlobs(["foo/"])); + + // Assert + expect(result).toStrictEqual(["**/foo/**"]); + }); +}); diff --git a/src/__tests__/integration_test.js b/src/__tests__/integration_test.js index ae1b75a1c..d37e0460b 100644 --- a/src/__tests__/integration_test.js +++ b/src/__tests__/integration_test.js @@ -36,11 +36,12 @@ describe("Integration Tests", () => { .replace(ancesdir(), ".") .replace(new RegExp(escapeRegExp(path.sep), "g"), "/"), ]) + // Finally, this has to be an actual glob, or it won't work. + .map(([name, dirPath]) => [name, `${dirPath}/**`]) .sort() ); }; const exampleGlobs = getExampleGlobs(); - console.log(exampleGlobs); it.each(exampleGlobs)( "should report example %s to match snapshot", @@ -56,6 +57,7 @@ describe("Integration Tests", () => { comments: ["//", "#", "{/*"], dryRun: false, excludeGlobs: ["**/excluded/**"], + ignoreFiles: [], json: false, }, stringLogger, @@ -81,6 +83,7 @@ describe("Integration Tests", () => { comments: ["//", "#", "{/*"], dryRun: true, excludeGlobs: ["**/excluded/**"], + ignoreFiles: [], json: false, }, stringLogger, @@ -106,6 +109,7 @@ describe("Integration Tests", () => { comments: ["//", "#", "{/*"], dryRun: false, excludeGlobs: ["**/excluded/**"], + ignoreFiles: [], json: true, }, stringLogger, diff --git a/src/__tests__/parse-file_test.js b/src/__tests__/parse-file_test.js index b529aa721..f8b76ecbf 100644 --- a/src/__tests__/parse-file_test.js +++ b/src/__tests__/parse-file_test.js @@ -52,6 +52,7 @@ describe("#parseFile", () => { rootMarker: "rootmarker", dryRun: false, excludeGlobs: [], + ignoreFiles: [], json: false, }; @@ -74,6 +75,7 @@ describe("#parseFile", () => { rootMarker: null, dryRun: false, excludeGlobs: [], + ignoreFiles: [], json: false, }; @@ -107,6 +109,7 @@ describe("#parseFile", () => { rootMarker: null, dryRun: false, excludeGlobs: [], + ignoreFiles: [], json: false, }; @@ -137,6 +140,7 @@ describe("#parseFile", () => { rootMarker: null, dryRun: false, excludeGlobs: [], + ignoreFiles: [], json: false, }; @@ -177,6 +181,7 @@ describe("#parseFile", () => { rootMarker: null, dryRun: false, excludeGlobs: [], + ignoreFiles: [], json: false, }; @@ -214,6 +219,7 @@ describe("#parseFile", () => { rootMarker: null, dryRun: false, excludeGlobs: [], + ignoreFiles: [], json: false, }; @@ -250,6 +256,7 @@ describe("#parseFile", () => { rootMarker: null, dryRun: false, excludeGlobs: [], + ignoreFiles: [], json: false, }; @@ -310,6 +317,7 @@ describe("#parseFile", () => { rootMarker: null, dryRun: false, excludeGlobs: [], + ignoreFiles: [], json: false, }; @@ -350,6 +358,7 @@ describe("#parseFile", () => { rootMarker: null, dryRun: false, excludeGlobs: [], + ignoreFiles: [], json: false, }; @@ -388,6 +397,7 @@ describe("#parseFile", () => { rootMarker: null, dryRun: false, excludeGlobs: [], + ignoreFiles: [], json: false, }; @@ -422,6 +432,7 @@ describe("#parseFile", () => { rootMarker: null, dryRun: false, excludeGlobs: [], + ignoreFiles: [], json: false, }; const promise = parseFile(options, "file.js", false); @@ -489,6 +500,7 @@ describe("#parseFile", () => { rootMarker: null, dryRun: false, excludeGlobs: [], + ignoreFiles: [], json: false, }; const promise = parseFile(options, "file.js", false); diff --git a/src/check-sync.js b/src/check-sync.js index 87e60b9be..7973a441d 100644 --- a/src/check-sync.js +++ b/src/check-sync.js @@ -23,8 +23,9 @@ export default async function checkSync( if (options.autoFix && options.dryRun) { log.info("DRY-RUN: Files will not be modified"); } - const {includeGlobs, excludeGlobs} = options; - const files = await getFiles(includeGlobs, excludeGlobs, log); + + const {includeGlobs, excludeGlobs, ignoreFiles} = options; + const files = await getFiles(includeGlobs, excludeGlobs, ignoreFiles, log); if (files.length === 0) { log.error("No matching files"); diff --git a/src/cli.js b/src/cli.js index b9aed6f97..11e1403d1 100644 --- a/src/cli.js +++ b/src/cli.js @@ -10,24 +10,8 @@ import Logger from "./logger.js"; import ExitCodes from "./exit-codes.js"; import logHelp from "./help.js"; import defaultArgs from "./default-args.js"; -import parseGitIgnore from "parse-gitignore"; import {version} from "../package.json"; -const ignoreFilesToExcludes = (ignoreFilesArg: string): Array => { - const ignoreFiles = ignoreFilesArg.split(",").filter((c) => !!c); - if ( - ignoreFilesArg === defaultArgs.ignoreFiles && - !fs.existsSync(ignoreFilesArg) - ) { - return []; - } - - return ignoreFiles - .map((file) => fs.readFileSync(file)) - .map((content: Buffer) => parseGitIgnore(content)) - .reduce((prev, current: Array) => [...prev, ...current], []); -}; - /** * Run the command line. * @@ -106,25 +90,26 @@ export const run = (launchFilePath: string): void => { ); const comments = ((args.comments: any): string) - .split(",") + .split(" ") .filter((c) => !!c); const ignoreGlobs = ((args.ignore: any): string) - .split(",") + .split(";") .filter((c) => !!c); - const ignoreFileGlobs = args.noIgnoreFile + const ignoreFiles = args.noIgnoreFile ? [] - : ignoreFilesToExcludes((args.ignoreFiles: any)); - const excludeGlobs = [...ignoreGlobs, ...ignoreFileGlobs]; + : ((args.ignoreFiles: any): string).split(",").filter((c) => !!c); // Make sure we have something to search, so default to current working // directory if no globs are given. - const includeGlobs = args._ && args._.length > 0 ? args._ : [process.cwd()]; + const includeGlobs = + args._ && args._.length > 0 ? args._ : [`${process.cwd()}/**`]; checkSync( { includeGlobs, - excludeGlobs, + excludeGlobs: ignoreGlobs, + ignoreFiles, autoFix: args.updateTags === true, json: args.json === true, comments, diff --git a/src/default-args.js b/src/default-args.js index 839779b5e..97e88181a 100644 --- a/src/default-args.js +++ b/src/default-args.js @@ -2,7 +2,7 @@ const defaultArgs = { updateTags: false, json: false, - comments: `${["#", "//", "{/*"].sort().join(",")}`, + comments: `${["#", "//", "{/*"].sort().join(" ")}`, ignore: "", help: false, dryRun: false, diff --git a/src/get-files.js b/src/get-files.js index 0adf83c2b..e3989e6fc 100644 --- a/src/get-files.js +++ b/src/get-files.js @@ -1,64 +1,46 @@ // @flow import glob from "fast-glob"; import path from "path"; +import ignoreFilesToExcludeGlobs from "./ignore-files-to-exclude-globs.js"; import type {ILog} from "./types.js"; /** - * Following gitignore format https://git-scm.com/docs/gitignore#_pattern_format + * Expand the given globs and ignore files into a list of files. * - * /foo Ignore root (not sub) file and dir and its paths underneath. /foo, /foo/** - * /foo/ Ignore root (not sub) foo dir and its paths underneath. /foo/** - * foo Ignore (root/sub) foo files and dirs and their paths underneath. foo, ** /foo/** - * foo/ Ignore (root/sub) foo dirs and their paths underneath. ** /foo/** - */ -function* turnIgnoresToGlobs(globs: Array): Iterator { - const normalizeSeparators = (g: string): string => - g.split(path.sep).join("/"); - for (const glob of globs) { - if (glob.startsWith("/")) { - yield normalizeSeparators(path.join(glob, "**")); - if (!glob.endsWith("/")) { - yield glob; - } - } else { - yield normalizeSeparators(path.join("**", glob, "**")); - if (!glob.endsWith("/")) { - yield glob; - } - } - } -} - -/** - * Expand the given globs into files. - * - * @param {Array} globs The globs to expand. + * @param {Array} includeGlobs The include globs to expand. + * @param {Array} excludeGlobs The exclude globs to expand. + * @param {Array} ignoreFiles The ignore files to expand. + * @param {ILog} log A log to record things */ export default async function getFiles( includeGlobs: Array, excludeGlobs: Array, - log: ILog, + ignoreFiles: Array, + log?: ILog, ): Promise> { - const includePatterns = Array.from(turnIgnoresToGlobs(includeGlobs)); - log.verbose( - () => `Include globs: ${JSON.stringify(includePatterns, null, " ")}`, + const ignoreFileGlobs = ignoreFilesToExcludeGlobs(ignoreFiles); + const allExcludeGlobs = Array.from( + new Set([...excludeGlobs, ...ignoreFileGlobs]), + ); + + log?.verbose( + () => `Include globs: ${JSON.stringify(includeGlobs, null, " ")}`, ); - const excludePatterns = Array.from(turnIgnoresToGlobs(excludeGlobs)); - log.verbose( - () => `Exclude globs: ${JSON.stringify(excludePatterns, null, " ")}`, + log?.verbose( + () => `Exclude globs: ${JSON.stringify(allExcludeGlobs, null, " ")}`, ); // Now let's match the patterns and see what files we get. - const paths = await glob(includePatterns, { + const paths = await glob(includeGlobs, { onlyFiles: true, absolute: true, - ignore: excludePatterns, + ignore: allExcludeGlobs, }); const sortedPaths = paths .map((p) => p.replace(new RegExp("/", "g"), path.sep)) .sort(); - log.verbose( + log?.verbose( () => `Discovered paths: ${JSON.stringify(sortedPaths, null, " ")}`, ); return sortedPaths; diff --git a/src/help.js b/src/help.js index 56da4846b..432a131a7 100644 --- a/src/help.js +++ b/src/help.js @@ -49,15 +49,15 @@ Where: ## Arguments - \`--comments,-c\` A string containing comma-separated tokens that + \`--comments,-c\` A string containing space-separated tokens that indicate the start of lines where tags appear. - Defaults to \`"//,#"\`. + Defaults to \`"// #"\`. \`--dry-run,-n\` Ignored unless supplied with \`--update-tags\`. \`--help,-h\` Outputs this help text. - \`--ignore,-i\` A string containing comma-separated globs that identify + \`--ignore,-i\` A string containing semi-colon-separated globs that identify files that should not be checked. \`--ignore-files\` A comma-separated list of .gitignore-like files that diff --git a/src/ignore-files-to-exclude-globs.js b/src/ignore-files-to-exclude-globs.js new file mode 100644 index 000000000..e4e3b15cd --- /dev/null +++ b/src/ignore-files-to-exclude-globs.js @@ -0,0 +1,38 @@ +// @flow +import fs from "fs"; +import parseGitIgnore from "parse-gitignore"; +import ignoreFormatToGlobs from "./ignore-format-to-globs.js"; +import defaultArgs from "./default-args.js"; + +export default ( + ignoreFiles: $ReadOnlyArray, +): $ReadOnlyArray => { + // If we are only processing the default ignore file and it doesn't exist, + // we can just return an empty array. + if ( + ignoreFiles.length === 1 && + ignoreFiles[0] === defaultArgs.ignoreFiles && + !fs.existsSync(ignoreFiles[0]) + ) { + return []; + } + + // TODO: We need to glob some of these and then we need to expand their + // ignores based on their file location. + + // TODO: Use Promise.all and async reads to see if some of this can be + // parallelized. + const allIgnores = ignoreFiles + // Read the file - this currently assumes it exists, we may want + // to consider skipping over ignore files that don't exist. + .map((file) => fs.readFileSync(file)) + // Parse it as .gitignore syntax. + .map((content: Buffer) => parseGitIgnore(content)) + // Flatten our array of arrays into a single array. + .reduce((prev, current: Array) => [...prev, ...current], []); + + const allIgnoresWithoutDuplicates = Array.from(new Set(allIgnores)); + + // Transform ignore syntax to globs. + return Array.from(ignoreFormatToGlobs(allIgnoresWithoutDuplicates)); +}; diff --git a/src/ignore-format-to-globs.js b/src/ignore-format-to-globs.js new file mode 100644 index 000000000..3454d2b07 --- /dev/null +++ b/src/ignore-format-to-globs.js @@ -0,0 +1,30 @@ +// @flow +import path from "path"; + +const normalizeSeparators = (g: string): string => g.split(path.sep).join("/"); + +/** + * Following gitignore format https://git-scm.com/docs/gitignore#_pattern_format + * + * /foo Only root (not sub) file and dir and its paths underneath. /foo, /foo/** + * /foo/ Only root (not sub) foo dir and its paths underneath. /foo/** + * foo Both root and sub foo files and dirs and their paths underneath. foo, ** /foo/** + * foo/ Both root and sub foo dirs and their paths underneath. ** /foo/** + */ +export default function* ignoreFormatToGlobs( + ignores: Array, +): Iterator { + for (const ignore of ignores) { + if (ignore.startsWith("/")) { + yield normalizeSeparators(path.join(ignore, "**")); + if (!ignore.endsWith("/")) { + yield ignore; + } + } else { + yield normalizeSeparators(path.join("**", ignore, "**")); + if (!ignore.endsWith("/")) { + yield ignore; + } + } + } +} diff --git a/src/main.js b/src/main.js new file mode 100644 index 000000000..c3b555518 --- /dev/null +++ b/src/main.js @@ -0,0 +1,4 @@ +// @flow +export {run} from "./cli.js"; + +// TODO: Export API diff --git a/src/output-sink.js b/src/output-sink.js index da1a6e000..e1f347518 100644 --- a/src/output-sink.js +++ b/src/output-sink.js @@ -151,7 +151,7 @@ export default class OutputSink { */ const updateCommandParts = []; updateCommandParts.push(getLaunchString()); - const commentsArg = this._options.comments.sort().join(","); + const commentsArg = this._options.comments.sort().join(" "); if (commentsArg !== defaultArgs.comments) { updateCommandParts.push("-c"); updateCommandParts.push(`"${commentsArg}"`); diff --git a/src/types.js b/src/types.js index 6829c9611..64e47ae31 100644 --- a/src/types.js +++ b/src/types.js @@ -183,12 +183,54 @@ export type normalizePathFn = (relativeFile: string) => { }; export type Options = { + /** + * The paths and globs for identifying files that are to be processed. + */ includeGlobs: Array, + + /** + * The globs for files that are to be ignored. + */ excludeGlobs: Array, + + /** + * .gitignore-syntax files indicating files that are to be ignored. + * + * Absolute and relative paths (i.e. "/user/.gitignore" or "./.gitignore") + * are treated as exact matches to a single file. + * + * File names like ".gitignore" are interpreted as being files to use in + * each folder processed. + * + * TODO: Support that second scenario (see #636) + */ + ignoreFiles: Array, + + /** + * When true, any fixable violations should be fixed automatically. + */ autoFix: boolean, + + /** + * When true, the details of the processing are returned to stdout as a + * JSON format string. + */ json: boolean, + + /** + * The comment styles to be supported. + */ comments: Array, + + /** + * When true, destructive actions such as auto-fixes are not actually + * written. + */ dryRun: boolean, + + /** + * The name of the marker file that identifies the root of sync-tag paths. + */ rootMarker?: ?string, };