Skip to content

Commit

Permalink
chore: add XO linter (#98)
Browse files Browse the repository at this point in the history
  • Loading branch information
LitoMore authored Apr 26, 2024
1 parent b5ed659 commit d6f1444
Show file tree
Hide file tree
Showing 37 changed files with 7,160 additions and 3,200 deletions.
25 changes: 0 additions & 25 deletions .eslintrc.yaml

This file was deleted.

14 changes: 5 additions & 9 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,10 @@ jobs:
os:
- ubuntu-latest
node-version:
- 12.22.0
- 14.17.0
- 16.0.0
- 18.0.0
- 20.0.0
- 12
- 14
- 16
- 18
- 19
- 20
include:
- os: macos-latest
Expand All @@ -52,9 +46,11 @@ jobs:
with:
cache: npm
node-version: ${{ matrix.node-version }}
- name: Install compatible npm version
run: npm install --global [email protected]
- name: Use specific version of npm for Node.js 12 & 14
if: matrix.node-version < 16
run: npm install -g npm@8
- name: Install dependencies
run: npm ci
# Use `--engine-strict=false` to ignore engine errors from XO dependencies
run: npm ci --engine-strict=false
- name: Test
run: npm test
43 changes: 43 additions & 0 deletions .xo-config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"prettier": true,
"space": 4,
"plugins": ["import"],
"ignores": [
"test/projects/broken/broken-svglint-config.js"
],
"rules": {
"unicorn/prefer-event-target": "warn",
"n/file-extension-in-import": "off",
"sort-imports": [
"error",
{
"ignoreCase": false,
"ignoreDeclarationSort": true,
"ignoreMemberSort": false,
"memberSyntaxSortOrder": ["none", "all", "multiple", "single"],
"allowSeparatedGroups": false
}
],
"import/no-named-as-default": "off",
"import/extensions": "off",
"import/order": [
"error",
{
"groups": ["builtin", "external", "parent", "sibling", "index"],
"alphabetize": {
"order": "asc",
"caseInsensitive": true
},
"warnOnUnassignedImports": true,
"newlines-between": "never"
}
],
"no-console": ["error", { "allow": ["warn", "error"] }]
},
"overrides": [
{
"files": ["test/**/*"],
"envs": "mocha"
}
]
}
172 changes: 90 additions & 82 deletions bin/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,20 @@
* @fileoverview The CLI that is executed from a terminal.
* Acts as an interface to the JS API
*/
import path from "path";
import process from "process";
import gui from "../src/cli/gui.js";
import Logger from "../src/lib/logger.js";
import SVGLint from "../src/svglint.js";
import path from 'node:path';
import process from 'node:process';
import glob from 'glob';
import meow from 'meow';
import {loadConfigurationFile} from '../src/cli/config.js';
import GUI from '../src/cli/gui.js';
import {chalk} from '../src/cli/util.js';
import logging from '../src/lib/logger.js';
import SVGLint from '../src/svglint.js';
// @ts-ignore
import { loadConfigurationFile } from "../src/cli/config.js";
import meow from "meow";
import { chalk } from "../src/cli/util.js";
import glob from "glob";

const GUI = new gui();
const gui = new GUI();

const logger = Logger("");
const logger = logging('');

const EXIT_CODES = Object.freeze({
success: 0,
Expand All @@ -26,147 +26,155 @@ const EXIT_CODES = Object.freeze({
configuration: 4,
});

// used by meow's loud reject
// eslint-disable-next-line no-console
// Used by meow's loud reject

console.error = logger.error.bind(logger);

// Pretty logs all errors, then exits
process.on("uncaughtException", err => {
logger.error(err);
process.on('uncaughtException', (error) => {
logger.error(error);
process.exit(EXIT_CODES.unexpected);
});

// Handle SIGINT
process.on("SIGINT", () => {
process.on('SIGINT', () => {
process.exit(EXIT_CODES.interrupted);
});

// Generates the CLI binding using meow
const cli = meow(`
${chalk.yellow("Usage:")}
${chalk.bold("svglint")} [--config config.js] [--ci] [--debug] ${chalk.bold("file1.svg file2.svg")}
${chalk.bold("svglint")} --stdin [--config config.js] [--ci] [--debug] < ${chalk.bold("file1.svg")}
${chalk.yellow("Options:")}
${chalk.bold("--help")} Display this help text
${chalk.bold("--version")} Show the current SVGLint version
${chalk.bold("--config, -c")} Specify the config file. Defaults to '.svglintrc.js'
${chalk.bold("--debug, -d")} Show debug logs
${chalk.bold("--ci, -C")} Only output to stdout once, when linting is finished
${chalk.bold("--stdin")} Read an SVG from stdin`, {
importMeta: import.meta,
flags: {
config: { type: "string", alias: "c", },
debug: { type: "boolean", alias: "d" },
ci: { type: "boolean", alias: "C" },
stdin: { type: "boolean" }
}
});

process.on("exit", () => {
GUI.finish();
const cli = meow(
`
${chalk.yellow('Usage:')}
${chalk.bold('svglint')} [--config config.js] [--ci] [--debug] ${chalk.bold('file1.svg file2.svg')}
${chalk.bold('svglint')} --stdin [--config config.js] [--ci] [--debug] < ${chalk.bold('file1.svg')}
${chalk.yellow('Options:')}
${chalk.bold('--help')} Display this help text
${chalk.bold('--version')} Show the current SVGLint version
${chalk.bold('--config, -c')} Specify the config file. Defaults to '.svglintrc.js'
${chalk.bold('--debug, -d')} Show debug logs
${chalk.bold('--ci, -C')} Only output to stdout once, when linting is finished
${chalk.bold('--stdin')} Read an SVG from stdin`,
{
importMeta: import.meta,
flags: {
config: {type: 'string', alias: 'c'},
debug: {type: 'boolean', alias: 'd'},
ci: {type: 'boolean', alias: 'C'},
stdin: {type: 'boolean'},
},
},
);

process.on('exit', () => {
gui.finish();
});

/** CLI main function */
(async function(){
// eslint-disable-next-line unicorn/prefer-top-level-await
(async function () {
if (cli.flags.debug) {
Logger.setLevel(Logger.LEVELS.debug);
logging.setLevel(logging.LEVELS.debug);
}
GUI.setCI(cli.flags.ci);

// load the config
let configObj;
gui.setCI(cli.flags.ci);

// Load the config
let configObject;
try {
configObj = await loadConfigurationFile(cli.flags.config);
if (configObj === null) {
logger.debug("No configuration file found");
configObject = await loadConfigurationFile(cli.flags.config);
if (configObject === null) {
logger.debug('No configuration file found');
if (cli.flags.config) {
logger.error("Configuration file not found");
logger.error('Configuration file not found');
process.exit(EXIT_CODES.configuration);
} else {
configObj = {};
configObject = {};
}
} else if (configObj === undefined) {
logger.error("Default export missing from configuration file (use `export default {...}` or `module.exports = {...}`)");
} else if (configObject === undefined) {
logger.error(
'Default export missing from configuration file (use `export default {...}` or `module.exports = {...}`)',
);
process.exit(EXIT_CODES.configuration);
}
} catch (e) {
logger.error(`Failed to parse config: ${e.stack}`);
} catch (error) {
logger.error(`Failed to parse config: ${error.stack}`);
process.exit(EXIT_CODES.configuration);
}

if (cli.flags.stdin) {
// lint what's provided on stdin
// Lint what's provided on stdin
const chunks = [];

process.stdin.on("readable", () => {
process.stdin.on('readable', () => {
let chunk;
while (null !== (chunk = process.stdin.read())) {
while ((chunk = process.stdin.read()) !== null) {
chunks.push(chunk);
}
});

process.stdin.on("end", () => {
SVGLint.lintSource(chunks.join(""), configObj)
.then(linting => {
// handle case where linting failed (e.g. invalid file)
process.stdin.on('end', () => {
SVGLint.lintSource(chunks.join(''), configObject)
.then((linting) => {
// Handle case where linting failed (e.g. invalid file)
if (!linting) {
process.exit(EXIT_CODES.success);
}

// otherwise add it to GUI and wait for it to finish
GUI.addLinting(linting);
linting.on("done", () => {
// Otherwise add it to GUI and wait for it to finish
gui.addLinting(linting);
linting.on('done', () => {
if (linting.state === linting.STATES.error) {
process.exit(EXIT_CODES.violations);
} else {
process.exit(EXIT_CODES.success);
}
});
})
.catch(e => {
logger.error("Failed to lint\n", e);
.catch((error) => {
logger.error('Failed to lint\n', error);
});
});
} else {
// lint all the CLI specified files
// Lint all the CLI specified files
const files = cli.input
.map(v => glob.sync(v))
.reduce((a, v) => a.concat(v), [])
.map(v => path.resolve(process.cwd(), v));
// keep track so we know when every linting has finished
.flatMap((v) => glob.sync(v))
.map((v) => path.resolve(process.cwd(), v));
// Keep track so we know when every linting has finished
let hasErrors = false;
let activeLintings = files.length;
const onLintingDone = () => {
--activeLintings;
logger.debug("Linting done,", activeLintings, "to go");
logger.debug('Linting done,', activeLintings, 'to go');
if (activeLintings <= 0) {
process.exit(
hasErrors ? EXIT_CODES.violations : EXIT_CODES.success
hasErrors ? EXIT_CODES.violations : EXIT_CODES.success,
);
}
};
files.forEach(filePath => {
SVGLint.lintFile(filePath, configObj)
.then(linting => {
// handle case where linting failed (e.g. invalid file)

for (const filePath of files) {
SVGLint.lintFile(filePath, configObject)
.then((linting) => {
// Handle case where linting failed (e.g. invalid file)
if (!linting) {
onLintingDone();
return;
}

// otherwise add it to GUI and wait for it to finish
GUI.addLinting(linting);
linting.on("done", () => {
// Otherwise add it to GUI and wait for it to finish
gui.addLinting(linting);
linting.on('done', () => {
if (linting.state === linting.STATES.error) {
hasErrors = true;
}

onLintingDone();
});
})
.catch(e => {
logger.error("Failed to lint file", filePath, "\n", e);
.catch((error) => {
logger.error('Failed to lint file', filePath, '\n', error);
});
});
}
}
})();
Loading

0 comments on commit d6f1444

Please sign in to comment.