├── .eslintrc.json
├── .gitignore
├── .prettierignore
├── .travis.yml
├── LICENSE
├── README.md
├── cli.js
├── media
├── demo1.png
└── logo.png
├── package.json
├── src
├── display.js
├── index.js
└── template.js
└── test
├── __snapshots__
└── index.test.js.snap
├── index.test.js
└── testFolder
└── testAgain
├── test.txt
└── testForSake.js
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "plugins": ["prettier"],
4 | "rules": {
5 | "prettier/prettier": "error"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | coverage/
3 | ./test/coverage
4 | yarn.lock
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | src/template.js
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "8"
4 | install:
5 | - npm install
6 | script:
7 | - npm run test
8 |
9 |
10 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Paul Rosset
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | > Make sure the file-names stay the same, control them! 👁
11 |
12 | [](https://travis-ci.org/PaulRosset/linter-farch)
13 | [](https://badge.fury.io/js/linter-farch)
14 | [](https://travis-ci.org/PaulRosset/linter-farch)
15 |
16 | ## Motivation
17 |
18 | More and more frameworks that have been created recently gave the possibility to the user to write content in markdown, like [Gatsby](https://github.com/gatsbyjs/gatsby) or [Docusaurus](https://github.com/facebook/docusaurus), but sometimes if you collaborate with multiples people on these markdown files, keeping a clean file-name is more important than ever. That's why I created this tiny linter to force people to respect a file-name architecture in order the keep everything clean and understandable.
19 | Of course, many other usages can be considered.
20 |
21 | ## Install
22 |
23 | ```sh
24 | yarn add --dev linter-farch
25 | ```
26 |
27 | ## Usage
28 |
29 | Once installed, a small and quick configuration is needed in the `package.json` file.
30 | The `package.json` file is used here, to avoid creating another file with a purpose of configuration.
31 |
32 | ### Configuration:
33 |
34 | For the configuration, two possibles way can be taken, the first is the `package.json` file like below (essentially for the JS project and if you don't want to create another config file):
35 |
36 | In the `package.json` file:
37 |
38 | ```json
39 | {
40 | "farch": {
41 | "src": "([a-z]*-[0-9]{4})[.]*[a-z]*",
42 | "src/utilities": "[a-z]*",
43 | "src/utilities/*.js": "[a-z]"
44 | }
45 | }
46 | ```
47 |
48 | > You can use [`glob`](http://man7.org/linux/man-pages/man3/glob.3.html) as key/path to provide more flexibility to capture the wanted files.
49 |
50 | * Creating regex can be hard or simply boring, that's why you can simply put template placeholder like this:
51 |
52 | ```json
53 | {
54 | "farch": {
55 | "src": "([a-z]*-[0-9]{4})[.]*[a-z]*",
56 | "src/utilities": ["LOWER_CAMEL_CASE_JS", "[a-z]*"],
57 | "src/utilities/*.js": "[a-z]"
58 | }
59 | }
60 | ```
61 |
62 | You can find any template placeholder already created [here](https://github.com/PaulRosset/linter-farch/blob/master/src/template.js), feel free to contribute by adding more template/placeholder regex. The keys have to be of the following form: "XXX*XXX_XXX_YY", where \_XXX* is the name and _YY_ the extension of the file that we want to test.
63 |
64 | ---
65 |
66 | But, there is still the possibility to create a `farch.json` config file at the root of the project, essentially for the non-js project or if you don't want to put the configuration in your `package.json`.
67 |
68 | ```json
69 | {
70 | "farch": {
71 | "src": "([a-z]*-[0-9]{4})[.]*[a-z]*",
72 | "src/utilities": "[a-z]*"
73 | }
74 | }
75 | ```
76 |
77 | > `farch.json` file have the priority over the `package.json` file.
78 |
79 | Inside the `farch` property, insert the directory that you want to test:
80 | Pass as `key`, the path from the root directory to the target directory, then in value pass `regex` to match.
81 |
82 | **Then, you are all set!**
83 |
84 | ### Execution
85 |
86 | To avoid creating tons of rules if you have a lot of directory nested and they apply to the same assertion you can pass `-R`, hence it will recursively check all the directory.
87 |
88 | At the root of your project:
89 |
90 | ```sh
91 | npx farch
92 | ```
93 |
94 | or
95 |
96 | Insert it in your `package.json` file:
97 |
98 | ```json
99 | {
100 | "scripts": {
101 | "test": "farch ((-R))"
102 | }
103 | }
104 | ```
105 |
106 | **And run `CI` on it !**
107 |
108 | ### Output
109 |
110 |
111 |

112 |
113 |
114 | ## License
115 |
116 | MIT Paul Rosset
117 |
--------------------------------------------------------------------------------
/cli.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | "use strict";
3 |
4 | const meow = require("meow");
5 | const chalk = require("chalk");
6 | const getInput = require("./src/index");
7 | const path = require("path");
8 | const fs = require("fs");
9 | const loadJsonFile = require("load-json-file");
10 | const { displayRec } = require("./src/display");
11 |
12 | const cli = meow(
13 | `
14 | Usage
15 | $ farch
16 | Examples
17 | $ farch
18 | Options
19 | -R Allow to perform assertion recursively
20 | `,
21 | {
22 | flags: {
23 | R: {
24 | type: "boolean"
25 | }
26 | }
27 | }
28 | );
29 |
30 | const main = () => {
31 | try {
32 | Promise.resolve()
33 | .then(() => {
34 | const data = fs.existsSync(path.resolve("farch.json"))
35 | ? loadJsonFile.sync(path.resolve("farch.json"))
36 | : loadJsonFile.sync(path.resolve("package.json"));
37 | return data;
38 | })
39 | .then(data => {
40 | return Promise.all(getInput(data, cli.flags)).then(report => report);
41 | })
42 | .then(dataToDisplay => {
43 | console.log(chalk`\n {bold ${"Report linter-farch:\n"}}`);
44 | displayRec(dataToDisplay);
45 | });
46 | } catch (e) {
47 | if (e) {
48 | console.log(chalk` {red.bold ${e.message}}`);
49 | return;
50 | }
51 | }
52 | };
53 |
54 | main();
55 |
--------------------------------------------------------------------------------
/media/demo1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PaulRosset/linter-farch/6aa8bacbc5aab54238894e4f00089c9734cb43b5/media/demo1.png
--------------------------------------------------------------------------------
/media/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PaulRosset/linter-farch/6aa8bacbc5aab54238894e4f00089c9734cb43b5/media/logo.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "linter-farch",
3 | "version": "1.3.0",
4 | "description": "Linter filenames, make sure the filenames stay the same!",
5 | "main": "src/index.js",
6 | "bin": {
7 | "farch": "cli.js"
8 | },
9 | "scripts": {
10 | "test": "eslint src/ && jest --coverage && codecov",
11 | "pretty": "prettier src/*.js --write"
12 | },
13 | "pre-commit": ["pretty"],
14 | "jest": {
15 | "coverageDirectory": "./test/coverage/",
16 | "collectCoverage": true
17 | },
18 | "repository": {
19 | "type": "git",
20 | "url": "git+https://github.com/PaulRosset/linter-farch.git"
21 | },
22 | "keywords": ["linter", "filenames", "filenames", "architecture", "lint-file"],
23 | "author": "Paul Rosset ",
24 | "license": "MIT",
25 | "bugs": {
26 | "url": "https://github.com/PaulRosset/linter-farch/issues"
27 | },
28 | "homepage": "https://github.com/PaulRosset/linter-farch#readme",
29 | "dependencies": {
30 | "chalk": "^2.4.1",
31 | "globby": "^8.0.1",
32 | "load-json-file": "^4.0.0",
33 | "meow": "^5.0.0"
34 | },
35 | "devDependencies": {
36 | "babel-eslint": "^8.2.3",
37 | "codecov": "^3.0.0",
38 | "eslint": "^4.19.1",
39 | "eslint-plugin-prettier": "^2.6.0",
40 | "jest": "^22.4.3",
41 | "pre-commit": "^1.2.2",
42 | "prettier": "^1.12.1"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/display.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const chalk = require("chalk");
4 |
5 | const reportFinal = {
6 | errors: 0,
7 | success: 0,
8 | all: 0
9 | };
10 |
11 | const displayRes = reportFinal => {
12 | console.log(chalk`
13 | {bold.white Tested files: ${reportFinal.all} tests}.
14 | \t\t {green.bold ${reportFinal.success} passed}.
15 | \t\t {red.bold ${reportFinal.errors} failed}.
16 | `);
17 | process.exit(reportFinal.errors > 0 ? 2 : 0);
18 | };
19 |
20 | const displayRec = report => {
21 | report.map((dir, key) => {
22 | const path = Object.keys(dir);
23 | console.log(chalk` Directory: {blue.bold ${path}}`);
24 | dir[path].map(file => {
25 | reportFinal.all += 1;
26 | if (!file.isCorrectSyntax) {
27 | console.log(chalk` {underline ${file.fileName}} - {red.bold ●}`);
28 | console.log(
29 | chalk` {red.bold ${file.fileName}} doesn't match /${
30 | file.assertRegex
31 | }/`
32 | );
33 | reportFinal.errors += 1;
34 | } else {
35 | console.log(
36 | chalk` {underline ${file.fileName}} - {green.bold ✓}`
37 | );
38 | reportFinal.success += 1;
39 | }
40 | });
41 | });
42 | displayRes(reportFinal);
43 | };
44 |
45 | module.exports = {
46 | displayRec
47 | };
48 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const globby = require("globby");
4 | const template = require("./template");
5 |
6 | const concatForRegex = tabOfRegex => {
7 | let test = "";
8 | for (const regex in tabOfRegex) {
9 | test += `(${tabOfRegex[regex]})`;
10 | }
11 | return test;
12 | };
13 |
14 | const assertFiles = (inputs, rec) => {
15 | return inputs.map(async file => {
16 | const filesToTest = await globby([file.path], {
17 | deep: rec ? true : false
18 | });
19 | return {
20 | [file.path]: filesToTest.map(fileToTest => {
21 | const fileSimple = fileToTest.split("/");
22 | const fileNameindex = fileSimple.length - 1;
23 | return {
24 | fileName: fileSimple[fileNameindex],
25 | pathFileName: fileToTest,
26 | isCorrectSyntax: new RegExp(
27 | Array.isArray(file.regex) ? concatForRegex(file.regex) : file.regex
28 | ).test(fileSimple[fileNameindex]),
29 | assertRegex: file.regex
30 | };
31 | })
32 | };
33 | });
34 | };
35 |
36 | module.exports = (config, opts) => {
37 | const { farch } = config;
38 | const inputs = [];
39 | for (const key in farch) {
40 | const isRegexArray = Array.isArray(farch[key]);
41 | if (!isRegexArray && typeof farch[key] !== "string")
42 | throw new TypeError("Patterns must be a string or an array of strings");
43 | if (isRegexArray && !farch[key].every(reg => typeof reg === "string"))
44 | throw new TypeError("Template element must be a string");
45 | inputs.push({
46 | path: key,
47 | regex: isRegexArray
48 | ? farch[key].map(
49 | templateInput =>
50 | template[templateInput] ? template[templateInput] : templateInput
51 | )
52 | : farch[key]
53 | });
54 | }
55 | if (inputs.length === 0) {
56 | throw new Error("No farch config found in farch.js or package.json!");
57 | } else {
58 | return assertFiles(inputs, opts.R);
59 | }
60 | };
61 |
62 | module.exports.concatForRegex = concatForRegex;
63 |
--------------------------------------------------------------------------------
/src/template.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 |
3 | module.exports = {
4 | LOWER_CAMEL_CASE_JS: "(^([a-z0-9]+?[A-Z]{1}[a-z0-9]*)+\.js$)",
5 | UPPER_CAMEL_CASE_JS: "(^((d)|([A-Z0-9][a-z0-9]+))*([A-Z])?\.js$)"
6 | };
7 |
--------------------------------------------------------------------------------
/test/__snapshots__/index.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Non Recursive testing Expect a error throw, because dir did not exist 1`] = `
4 | Array [
5 | Object {
6 | "dir": Array [],
7 | },
8 | ]
9 | `;
10 |
11 | exports[`Non Recursive testing Expect to not match first (.txt) and match the second (.js), use of template and non template in array 1`] = `"[{\\"test/testFolder\\":[]}]"`;
12 |
--------------------------------------------------------------------------------
/test/index.test.js:
--------------------------------------------------------------------------------
1 | const { concatForRegex } = require("../src/index");
2 | const apiFarch = require("../src/index");
3 |
4 | const pkg1 = {
5 | name: "new",
6 | version: "1.0.0",
7 | description: "",
8 | keywords: [],
9 | main: "src/index.js",
10 | dependencies: {
11 | react: "16.3.2",
12 | "react-dom": "16.3.2",
13 | "react-scripts": "1.1.4"
14 | },
15 | devDependencies: {},
16 | scripts: {
17 | start: "react-scripts start",
18 | build: "react-scripts build",
19 | test: "react-scripts test --env=jsdom",
20 | eject: "react-scripts eject"
21 | }
22 | };
23 |
24 | const pkg2 = {
25 | farch: {
26 | "test/testFolder/testAgain": "[a-z]*"
27 | }
28 | };
29 |
30 | const pkg3 = {
31 | farch: {
32 | "./media": "[0-9]"
33 | }
34 | };
35 |
36 | const pkg4 = {
37 | farch: {
38 | dir: "[0-9]"
39 | }
40 | };
41 |
42 | const pkg5 = {
43 | farch: {
44 | "test/testFolder/testAgain": "[0-9]",
45 | "test/testFolder": "[0-9]"
46 | }
47 | };
48 |
49 | const pkg6 = {
50 | farch: {
51 | "test/testFolder": ["LOWER_CAMEL_CASE_JS", "[a-z]*"]
52 | }
53 | };
54 |
55 | const pkgError = {
56 | farch: {
57 | "test/testFolder": [1]
58 | }
59 | };
60 |
61 | const pkgError2 = {
62 | farch: {
63 | "test/testFolder": 1
64 | }
65 | };
66 |
67 | describe("Configuration testing", () => {
68 | test("When there is no config provided via package.json", () => {
69 | expect(() => {
70 | const report = apiFarch(pkg1, { R: false });
71 | }).toThrow("No farch config found in farch.js or package.json!");
72 | });
73 | });
74 |
75 | describe("Non Recursive testing", () => {
76 | test("Expect a error throw, because dir did not exist", done => {
77 | Promise.all(apiFarch(pkg4, { R: false })).then(config => {
78 | expect(config).toMatchSnapshot();
79 | done();
80 | });
81 | });
82 |
83 | test("Expect to Match test directory filenames", done => {
84 | const expectedArray = [
85 | {
86 | "test/testFolder/testAgain": [
87 | {
88 | fileName: "test.txt",
89 | pathFileName: "test/testFolder/testAgain/test.txt",
90 | isCorrectSyntax: true,
91 | assertRegex: "[a-z]*"
92 | },
93 | {
94 | fileName: "testForSake.js",
95 | pathFileName: "test/testFolder/testAgain/testForSake.js",
96 | isCorrectSyntax: true,
97 | assertRegex: "[a-z]*"
98 | }
99 | ]
100 | }
101 | ];
102 | Promise.all(apiFarch(pkg2, { R: false })).then(config => {
103 | expect(config).toEqual(expect.arrayContaining(expectedArray));
104 | done();
105 | });
106 | });
107 |
108 | test("Expect to NOT match filename in root diretory", done => {
109 | const expectedArray = [
110 | {
111 | "./media": [
112 | {
113 | fileName: "demo1.png",
114 | pathFileName: "media/demo1.png",
115 | isCorrectSyntax: true,
116 | assertRegex: "[0-9]"
117 | },
118 | {
119 | fileName: "logo.png",
120 | pathFileName: "media/logo.png",
121 | isCorrectSyntax: false,
122 | assertRegex: "[0-9]"
123 | }
124 | ]
125 | }
126 | ];
127 | Promise.all(apiFarch(pkg3, { R: false })).then(config => {
128 | expect(config).toEqual(expect.arrayContaining(expectedArray));
129 | done();
130 | });
131 | });
132 |
133 | test("Expect to match without recursive", done => {
134 | const expectedArray = [
135 | {
136 | "test/testFolder/testAgain": [
137 | {
138 | fileName: "test.txt",
139 | pathFileName: "test/testFolder/testAgain/test.txt",
140 | isCorrectSyntax: false,
141 | assertRegex: "[0-9]"
142 | },
143 | {
144 | fileName: "testForSake.js",
145 | pathFileName: "test/testFolder/testAgain/testForSake.js",
146 | isCorrectSyntax: false,
147 | assertRegex: "[0-9]"
148 | }
149 | ]
150 | },
151 | { "test/testFolder": [] }
152 | ];
153 | Promise.all(apiFarch(pkg5, { R: false })).then(config => {
154 | expect(config).toEqual(expect.arrayContaining(expectedArray));
155 | done();
156 | });
157 | });
158 |
159 | test("Expect to not match first (.txt) and match the second (.js), use of template and non template in array", done => {
160 | Promise.all(apiFarch(pkg6, { R: false })).then(config => {
161 | expect(JSON.stringify(config)).toMatchSnapshot();
162 | done();
163 | });
164 | });
165 | });
166 |
167 | describe("Recursive testing", () => {
168 | test("Expect to match with recursive", done => {
169 | Promise.all(apiFarch(pkg5, { R: true })).then(config => {
170 | expect(config).toHaveLength(2);
171 | done();
172 | });
173 | });
174 | });
175 |
176 | describe("Handle Throw TypeError", () => {
177 | test("When The regex given is invalid, either not a Array of String or a String", () => {
178 | expect(() => {
179 | Promise.all(apiFarch(pkgError2, { R: true }));
180 | }).toThrow("Patterns must be a string or an array of strings");
181 | });
182 |
183 | test("Verify if the the array of string is composed uniquely of String either throw", () => {
184 | expect(() => {
185 | Promise.all(apiFarch(pkgError, { R: true }));
186 | }).toThrow("Template element must be a string");
187 | });
188 | });
189 |
190 | describe("Test utility function", () => {
191 | const array = ["a", "b"];
192 | const stringEncapsulatedFromArray = concatForRegex(array);
193 | expect(stringEncapsulatedFromArray).toEqual("(a)(b)");
194 | });
195 |
--------------------------------------------------------------------------------
/test/testFolder/testAgain/test.txt:
--------------------------------------------------------------------------------
1 | I'm a test file
--------------------------------------------------------------------------------
/test/testFolder/testAgain/testForSake.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PaulRosset/linter-farch/6aa8bacbc5aab54238894e4f00089c9734cb43b5/test/testFolder/testAgain/testForSake.js
--------------------------------------------------------------------------------