markuplint 2.0.0-rc.6 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +70 -20
- package/lib/api/index.d.mts +2 -0
- package/lib/api/index.mjs +2 -0
- package/lib/api/lint.d.mts +4 -0
- package/lib/api/lint.mjs +15 -0
- package/lib/api/ml-engine.d.mts +27 -0
- package/lib/api/ml-engine.mjs +232 -0
- package/lib/api/types.d.mts +43 -0
- package/lib/api/types.mjs +1 -0
- package/lib/api/v1.d.mts +51 -0
- package/lib/api/v1.mjs +39 -0
- package/lib/cli/bootstrap.d.mts +52 -0
- package/lib/cli/bootstrap.mjs +81 -0
- package/lib/cli/command.d.mts +3 -0
- package/lib/cli/command.mjs +50 -0
- package/lib/cli/create-rule/index.d.mts +1 -0
- package/lib/cli/create-rule/index.mjs +51 -0
- package/lib/cli/index.d.mts +1 -0
- package/lib/cli/index.mjs +64 -0
- package/lib/cli/index.spec.d.mts +1 -0
- package/lib/cli/index.spec.mjs +48 -0
- package/lib/cli/init/index.d.mts +1 -0
- package/lib/cli/init/index.mjs +266 -0
- package/lib/cli/init/install-module.d.mts +5 -0
- package/lib/cli/init/install-module.mjs +61 -0
- package/lib/cli/output.d.mts +3 -0
- package/lib/cli/output.mjs +31 -0
- package/lib/cli/prompt.d.mts +18 -0
- package/lib/cli/prompt.mjs +68 -0
- package/lib/debug.d.mts +3 -0
- package/lib/debug.mjs +10 -0
- package/lib/global-settings.d.mts +5 -0
- package/lib/global-settings.mjs +10 -0
- package/lib/i18n.d.mts +6 -0
- package/lib/i18n.mjs +19 -0
- package/lib/index.d.mts +8 -0
- package/lib/index.mjs +8 -0
- package/lib/reporter/index.d.mts +2 -0
- package/lib/reporter/index.mjs +2 -0
- package/lib/reporter/simple-reporter.d.mts +3 -0
- package/lib/reporter/simple-reporter.mjs +30 -0
- package/lib/reporter/standard-reporter.d.mts +3 -0
- package/lib/reporter/standard-reporter.mjs +43 -0
- package/lib/testing-tool/index.d.mts +35 -0
- package/lib/testing-tool/index.mjs +83 -0
- package/lib/types.d.mts +27 -0
- package/lib/types.mjs +1 -0
- package/lib/util.d.mts +19 -0
- package/lib/util.mjs +73 -0
- package/lib/v1.d.mts +4 -0
- package/lib/v1.mjs +4 -0
- package/media/screenshot01.png +0 -0
- package/package.json +14 -14
- package/tsconfig.tsbuildinfo +1 -1
- package/RESOLVE_CONFIG.md +0 -77
- package/lib/cli/head.d.ts +0 -1
- package/lib/cli/head.js +0 -31
- package/lib/reporter/utils.d.ts +0 -6
- package/lib/reporter/utils.js +0 -40
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import meow from 'meow';
|
|
2
|
+
export const help = `
|
|
3
|
+
Usage
|
|
4
|
+
$ markuplint <HTML file pathes (glob format)>
|
|
5
|
+
$ <stdout> | markuplint
|
|
6
|
+
|
|
7
|
+
Options
|
|
8
|
+
--config, -c FILE_PATH A configuration file path.
|
|
9
|
+
--fix, Fix HTML.
|
|
10
|
+
--format, -f FORMAT Output format. Support "JSON", "Simple" and "Standard". Default: "Standard".
|
|
11
|
+
--no-search-config No search a configure file automatically.
|
|
12
|
+
--ignore-ext Evaluate files that are received even though the type of extension.
|
|
13
|
+
--no-import-preset-rules No import preset rules.
|
|
14
|
+
--locale Locale of the message of violation. Default is an OS setting.
|
|
15
|
+
--no-color, Output no color.
|
|
16
|
+
--problem-only, -p Output only problems, without passeds.
|
|
17
|
+
--verbose Output with detailed information.
|
|
18
|
+
|
|
19
|
+
--init Initialize settings interactively.
|
|
20
|
+
--create-rule Add the scaffold of a custom rule.
|
|
21
|
+
|
|
22
|
+
--help, -h Show help.
|
|
23
|
+
--version, -v Show version.
|
|
24
|
+
|
|
25
|
+
Examples
|
|
26
|
+
$ markuplint verifyee.html --config path/to/.markuplintrc
|
|
27
|
+
$ cat verifyee.html | markuplint
|
|
28
|
+
`;
|
|
29
|
+
export const cli = meow(help, {
|
|
30
|
+
importMeta: import.meta,
|
|
31
|
+
flags: {
|
|
32
|
+
config: {
|
|
33
|
+
type: 'string',
|
|
34
|
+
alias: 'c',
|
|
35
|
+
},
|
|
36
|
+
fix: {
|
|
37
|
+
type: 'boolean',
|
|
38
|
+
default: false,
|
|
39
|
+
},
|
|
40
|
+
format: {
|
|
41
|
+
type: 'string',
|
|
42
|
+
alias: 'f',
|
|
43
|
+
},
|
|
44
|
+
searchConfig: {
|
|
45
|
+
type: 'boolean',
|
|
46
|
+
default: true,
|
|
47
|
+
},
|
|
48
|
+
ignoreExt: {
|
|
49
|
+
type: 'boolean',
|
|
50
|
+
default: false,
|
|
51
|
+
},
|
|
52
|
+
importPresetRules: {
|
|
53
|
+
type: 'boolean',
|
|
54
|
+
default: true,
|
|
55
|
+
},
|
|
56
|
+
locale: {
|
|
57
|
+
type: 'string',
|
|
58
|
+
},
|
|
59
|
+
color: {
|
|
60
|
+
type: 'boolean',
|
|
61
|
+
default: true,
|
|
62
|
+
},
|
|
63
|
+
problemOnly: {
|
|
64
|
+
type: 'boolean',
|
|
65
|
+
alias: 'p',
|
|
66
|
+
default: false,
|
|
67
|
+
},
|
|
68
|
+
verbose: {
|
|
69
|
+
type: 'boolean',
|
|
70
|
+
default: false,
|
|
71
|
+
},
|
|
72
|
+
init: {
|
|
73
|
+
type: 'boolean',
|
|
74
|
+
default: false,
|
|
75
|
+
},
|
|
76
|
+
createRule: {
|
|
77
|
+
type: 'boolean',
|
|
78
|
+
default: false,
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { resolveFiles } from '@markuplint/file-resolver';
|
|
4
|
+
import { MLEngine } from '../api/index.mjs';
|
|
5
|
+
import { log } from '../debug.mjs';
|
|
6
|
+
import { output } from './output.mjs';
|
|
7
|
+
export async function command(files, options) {
|
|
8
|
+
const fix = options.fix;
|
|
9
|
+
const configFile = options.config && path.join(process.cwd(), options.config);
|
|
10
|
+
const locale = options.locale;
|
|
11
|
+
const searchConfig = options.searchConfig;
|
|
12
|
+
const ignoreExt = options.ignoreExt;
|
|
13
|
+
const importPresetRules = options.importPresetRules;
|
|
14
|
+
const verbose = options.verbose;
|
|
15
|
+
const fileList = await resolveFiles(files);
|
|
16
|
+
if (log.enabled) {
|
|
17
|
+
log('File list: %O', fileList.map(f => f.path));
|
|
18
|
+
log('Config: %s', configFile || 'N/A');
|
|
19
|
+
log('Fix option: %s', fix);
|
|
20
|
+
}
|
|
21
|
+
let hasError = false;
|
|
22
|
+
for (const file of fileList) {
|
|
23
|
+
const engine = new MLEngine(file, {
|
|
24
|
+
configFile,
|
|
25
|
+
fix,
|
|
26
|
+
locale,
|
|
27
|
+
noSearchConfig: !searchConfig,
|
|
28
|
+
ignoreExt,
|
|
29
|
+
importPresetRules,
|
|
30
|
+
debug: verbose,
|
|
31
|
+
});
|
|
32
|
+
const result = await engine.exec();
|
|
33
|
+
if (!result) {
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (!hasError && result.violations.length) {
|
|
37
|
+
hasError = true;
|
|
38
|
+
}
|
|
39
|
+
if (fix) {
|
|
40
|
+
log('Overwrite file: %s', result.filePath);
|
|
41
|
+
await fs.writeFile(result.filePath, result.fixedCode, { encoding: 'utf8' });
|
|
42
|
+
process.stdout.write(`markuplint: Fix "${result.filePath}"\n`);
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
log('Output reports');
|
|
46
|
+
await output(result, options);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return hasError;
|
|
50
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function createRule(): Promise<void>;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import * as createRuleHelper from '@markuplint/create-rule-helper';
|
|
2
|
+
import c from 'cli-color';
|
|
3
|
+
import { write, head } from '../../util.mjs';
|
|
4
|
+
import { installModule } from '../init/install-module.mjs';
|
|
5
|
+
import { input, select, confirm } from '../prompt.mjs';
|
|
6
|
+
export async function createRule() {
|
|
7
|
+
write(head('Create a rule'));
|
|
8
|
+
write.break();
|
|
9
|
+
const purpose = await select({
|
|
10
|
+
message: 'What purpose do you create the rule for?',
|
|
11
|
+
choices: [
|
|
12
|
+
{ name: 'Add the rule to this project', value: 'ADD_TO_PROJECT' },
|
|
13
|
+
{ name: 'Create the rule and publish it as a package', value: 'PUBLISH_AS_PACKAGE' },
|
|
14
|
+
{ name: 'Contribute the new rule to markuplint core rules', value: 'CONTRIBUTE_TO_CORE' },
|
|
15
|
+
],
|
|
16
|
+
});
|
|
17
|
+
const name = await input('What is the name?', /^[a-z][a-z0-9]*(?:-[a-z][a-z0-9]*)*$/i);
|
|
18
|
+
const lang = purpose === 'CONTRIBUTE_TO_CORE'
|
|
19
|
+
? 'TYPESCRIPT'
|
|
20
|
+
: await select({
|
|
21
|
+
message: 'Which language will you implement?',
|
|
22
|
+
choices: [
|
|
23
|
+
{ name: 'TypeScript', value: 'TYPESCRIPT' },
|
|
24
|
+
{ name: 'JavaScript', value: 'JAVASCRIPT' },
|
|
25
|
+
],
|
|
26
|
+
});
|
|
27
|
+
const needTest = purpose === 'CONTRIBUTE_TO_CORE' ? true : await confirm('Do you need the test?', { initial: true });
|
|
28
|
+
const result = await createRuleHelper.createRuleHelper({ purpose, name, lang, needTest });
|
|
29
|
+
output(name, '📝', 'README.md', result.readme);
|
|
30
|
+
output(name, '📜', 'index', result.main);
|
|
31
|
+
if (result.test) {
|
|
32
|
+
output(name, '🖍 ', 'index.spec', result.test);
|
|
33
|
+
}
|
|
34
|
+
if (result.schemaJson) {
|
|
35
|
+
output(name, '⚙️ ', 'schema.json', result.schemaJson);
|
|
36
|
+
}
|
|
37
|
+
if (result.packageJson) {
|
|
38
|
+
output(name, '🎁 ', 'package.json', result.packageJson);
|
|
39
|
+
if (result.tsConfig) {
|
|
40
|
+
output(name, '💎 ', 'tsconfig.json', result.tsConfig);
|
|
41
|
+
}
|
|
42
|
+
await installModule(result.dependencies);
|
|
43
|
+
await installModule(result.devDependencies, true);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function output(name, icon, title, path) {
|
|
47
|
+
const _marker = c.xterm(39)('✔') + ' ';
|
|
48
|
+
const _title = (icon, title) => `${icon} ` + c.bold(`${name}/${title}`);
|
|
49
|
+
const _file = (path) => ' ' + c.cyanBright(path);
|
|
50
|
+
write(_marker + _title(icon, title) + _file(path));
|
|
51
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import getStdin from 'get-stdin';
|
|
2
|
+
import { verbosely } from '../debug.mjs';
|
|
3
|
+
import { cli } from './bootstrap.mjs';
|
|
4
|
+
import { command } from './command.mjs';
|
|
5
|
+
import { createRule } from './create-rule/index.mjs';
|
|
6
|
+
import { initialize } from './init/index.mjs';
|
|
7
|
+
(async () => {
|
|
8
|
+
if (cli.flags.v) {
|
|
9
|
+
cli.showVersion(); // And exit successfully.
|
|
10
|
+
}
|
|
11
|
+
if (cli.flags.h) {
|
|
12
|
+
cli.showHelp(0); // And exit successfully.
|
|
13
|
+
}
|
|
14
|
+
if (cli.flags.verbose) {
|
|
15
|
+
verbosely();
|
|
16
|
+
}
|
|
17
|
+
if (cli.flags.init) {
|
|
18
|
+
await initialize().catch(err => {
|
|
19
|
+
process.stderr.write(err + '\n');
|
|
20
|
+
process.exit(1);
|
|
21
|
+
});
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (cli.flags.createRule) {
|
|
25
|
+
await createRule().catch(err => {
|
|
26
|
+
process.stderr.write(err + '\n');
|
|
27
|
+
process.exit(1);
|
|
28
|
+
});
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const files = cli.input;
|
|
32
|
+
if (files.length) {
|
|
33
|
+
const hasError = await command(files, cli.flags).catch(err => {
|
|
34
|
+
throw err;
|
|
35
|
+
// process.exit(1);
|
|
36
|
+
});
|
|
37
|
+
process.exit(hasError ? 1 : 0);
|
|
38
|
+
}
|
|
39
|
+
if (usePipe()) {
|
|
40
|
+
getStdin()
|
|
41
|
+
.then(async (stdin) => {
|
|
42
|
+
if (stdin) {
|
|
43
|
+
const hasError = await command([{ sourceCode: stdin }], cli.flags).catch(err => {
|
|
44
|
+
process.stderr.write(err + '\n');
|
|
45
|
+
process.exit(1);
|
|
46
|
+
});
|
|
47
|
+
process.exit(hasError ? 1 : 0);
|
|
48
|
+
}
|
|
49
|
+
// result is empty
|
|
50
|
+
cli.showHelp(1);
|
|
51
|
+
})
|
|
52
|
+
.catch(reason => {
|
|
53
|
+
// eslint-disable-next-line no-console
|
|
54
|
+
console.warn(reason);
|
|
55
|
+
process.exit(1);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
cli.showHelp(1);
|
|
60
|
+
}
|
|
61
|
+
})();
|
|
62
|
+
function usePipe() {
|
|
63
|
+
return !process.stdin.isTTY && process.stdout.isTTY;
|
|
64
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { execa } from 'execa';
|
|
3
|
+
import { cli } from './bootstrap.mjs';
|
|
4
|
+
const entryFilePath = path.resolve(__dirname, '../../bin/markuplint');
|
|
5
|
+
describe('STDOUT Test', () => {
|
|
6
|
+
it('empty', async () => {
|
|
7
|
+
const resultPromise = execa(entryFilePath, []);
|
|
8
|
+
await expect(resultPromise).rejects.toThrow(cli.help);
|
|
9
|
+
});
|
|
10
|
+
it('version', async () => {
|
|
11
|
+
const { stdout } = await execa(entryFilePath, ['--version']);
|
|
12
|
+
expect(stdout).toBe(cli.pkg.version);
|
|
13
|
+
});
|
|
14
|
+
it('version', async () => {
|
|
15
|
+
const { stdout } = await execa(entryFilePath, ['-v']);
|
|
16
|
+
expect(stdout).toBe(cli.pkg.version);
|
|
17
|
+
});
|
|
18
|
+
it('version', async () => {
|
|
19
|
+
const { stdout } = await execa(entryFilePath, ['--help']);
|
|
20
|
+
const { stdout: stdoutShort } = await execa(entryFilePath, ['-h']);
|
|
21
|
+
expect(stdout).toBe(stdoutShort);
|
|
22
|
+
});
|
|
23
|
+
it('verify', async () => {
|
|
24
|
+
const targetFilePath = path.resolve(__dirname, '../../../../test/fixture/001.html');
|
|
25
|
+
const { stdout, exitCode } = await execa(entryFilePath, ['--no-color', targetFilePath]);
|
|
26
|
+
expect(stdout).toBe(`<markuplint> passed ${targetFilePath}`);
|
|
27
|
+
expect(exitCode).toBe(0);
|
|
28
|
+
});
|
|
29
|
+
it('verify --problem-only', async () => {
|
|
30
|
+
const targetFilePath = path.resolve(__dirname, '../../../../test/fixture/001.html');
|
|
31
|
+
const { stdout } = await execa(entryFilePath, ['--problem-only', targetFilePath]);
|
|
32
|
+
expect(stdout).toBe('');
|
|
33
|
+
});
|
|
34
|
+
it('verify and feilure', async () => {
|
|
35
|
+
const targetFilePath = path.resolve(__dirname, '../../../../test/fixture/002.html');
|
|
36
|
+
const { stdout, stderr, exitCode } = await execa(entryFilePath, ['--no-color', targetFilePath], {
|
|
37
|
+
reject: false,
|
|
38
|
+
});
|
|
39
|
+
expect(stdout).toBe('');
|
|
40
|
+
expect(stderr.split('\n').length).toBe(34);
|
|
41
|
+
expect(exitCode).toBe(1);
|
|
42
|
+
});
|
|
43
|
+
it('format', async () => {
|
|
44
|
+
const targetFilePath = path.resolve(__dirname, '../../../../test/fixture/001.html');
|
|
45
|
+
const { stdout } = await execa(entryFilePath, ['--format', 'JSON', targetFilePath]);
|
|
46
|
+
expect(stdout).toBe('[]');
|
|
47
|
+
});
|
|
48
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function initialize(): Promise<void>;
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import util from 'util';
|
|
4
|
+
import { mergeConfig } from '@markuplint/ml-config';
|
|
5
|
+
import { head, write, error } from '../../util.mjs';
|
|
6
|
+
import { confirm, confirmSequence, multiSelect } from '../prompt.mjs';
|
|
7
|
+
import { installModule } from './install-module.mjs';
|
|
8
|
+
const writeFile = util.promisify(fs.writeFile);
|
|
9
|
+
const ruleCategories = {
|
|
10
|
+
validation: {
|
|
11
|
+
message: 'Are you going to conformance check according to HTML standard?',
|
|
12
|
+
},
|
|
13
|
+
a11y: {
|
|
14
|
+
message: 'Are you going to do with accessibility better practices?',
|
|
15
|
+
},
|
|
16
|
+
'naming-convention': {
|
|
17
|
+
message: 'Are you going to set the convention about naming?',
|
|
18
|
+
},
|
|
19
|
+
style: {
|
|
20
|
+
message: 'Are you going to check for the code styles?',
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
const defaultRules = {
|
|
24
|
+
'attr-duplication': {
|
|
25
|
+
category: 'validation',
|
|
26
|
+
default: true,
|
|
27
|
+
},
|
|
28
|
+
'attr-equal-space-after': {
|
|
29
|
+
category: 'style',
|
|
30
|
+
default: true,
|
|
31
|
+
},
|
|
32
|
+
'attr-equal-space-before': {
|
|
33
|
+
category: 'style',
|
|
34
|
+
default: true,
|
|
35
|
+
},
|
|
36
|
+
'attr-spacing': {
|
|
37
|
+
category: 'style',
|
|
38
|
+
default: true,
|
|
39
|
+
},
|
|
40
|
+
'attr-value-quotes': {
|
|
41
|
+
category: 'style',
|
|
42
|
+
default: true,
|
|
43
|
+
},
|
|
44
|
+
'case-sensitive-attr-name': {
|
|
45
|
+
category: 'style',
|
|
46
|
+
default: true,
|
|
47
|
+
},
|
|
48
|
+
'case-sensitive-attr-value': {
|
|
49
|
+
category: 'style',
|
|
50
|
+
default: true,
|
|
51
|
+
},
|
|
52
|
+
'case-sensitive-tag-name': {
|
|
53
|
+
category: 'style',
|
|
54
|
+
default: true,
|
|
55
|
+
},
|
|
56
|
+
'character-reference': {
|
|
57
|
+
category: 'validation',
|
|
58
|
+
default: true,
|
|
59
|
+
},
|
|
60
|
+
'class-naming': {
|
|
61
|
+
category: 'naming-convention',
|
|
62
|
+
default: false,
|
|
63
|
+
recommendedValue: '/.+/',
|
|
64
|
+
},
|
|
65
|
+
'deprecated-attr': {
|
|
66
|
+
category: 'validation',
|
|
67
|
+
default: true,
|
|
68
|
+
},
|
|
69
|
+
'deprecated-element': {
|
|
70
|
+
category: 'validation',
|
|
71
|
+
default: true,
|
|
72
|
+
},
|
|
73
|
+
'disallowed-element': {
|
|
74
|
+
category: 'validation',
|
|
75
|
+
default: false,
|
|
76
|
+
},
|
|
77
|
+
doctype: {
|
|
78
|
+
category: 'validation',
|
|
79
|
+
default: true,
|
|
80
|
+
},
|
|
81
|
+
'end-tag': {
|
|
82
|
+
category: 'style',
|
|
83
|
+
default: true,
|
|
84
|
+
},
|
|
85
|
+
'id-duplication': {
|
|
86
|
+
category: 'validation',
|
|
87
|
+
default: true,
|
|
88
|
+
},
|
|
89
|
+
indentation: {
|
|
90
|
+
category: 'style',
|
|
91
|
+
default: false,
|
|
92
|
+
recommendedValue: 2,
|
|
93
|
+
},
|
|
94
|
+
'ineffective-attr': {
|
|
95
|
+
category: 'validation',
|
|
96
|
+
default: true,
|
|
97
|
+
},
|
|
98
|
+
'invalid-attr': {
|
|
99
|
+
category: 'validation',
|
|
100
|
+
default: true,
|
|
101
|
+
},
|
|
102
|
+
'landmark-roles': {
|
|
103
|
+
category: 'a11y',
|
|
104
|
+
default: true,
|
|
105
|
+
},
|
|
106
|
+
'no-boolean-attr-value': {
|
|
107
|
+
category: 'style',
|
|
108
|
+
default: true,
|
|
109
|
+
},
|
|
110
|
+
'no-default-value': {
|
|
111
|
+
category: 'style',
|
|
112
|
+
default: true,
|
|
113
|
+
},
|
|
114
|
+
'no-hard-code-id': {
|
|
115
|
+
category: 'style',
|
|
116
|
+
default: true,
|
|
117
|
+
},
|
|
118
|
+
'no-refer-to-non-existent-id': {
|
|
119
|
+
category: 'a11y',
|
|
120
|
+
default: true,
|
|
121
|
+
},
|
|
122
|
+
'no-use-event-handler-attr': {
|
|
123
|
+
category: 'style',
|
|
124
|
+
default: true,
|
|
125
|
+
},
|
|
126
|
+
'permitted-contents': {
|
|
127
|
+
category: 'validation',
|
|
128
|
+
default: true,
|
|
129
|
+
},
|
|
130
|
+
'required-attr': {
|
|
131
|
+
category: 'validation',
|
|
132
|
+
default: true,
|
|
133
|
+
},
|
|
134
|
+
'required-element': {
|
|
135
|
+
category: 'validation',
|
|
136
|
+
default: true,
|
|
137
|
+
},
|
|
138
|
+
'required-h1': {
|
|
139
|
+
category: 'a11y',
|
|
140
|
+
default: true,
|
|
141
|
+
},
|
|
142
|
+
'wai-aria': {
|
|
143
|
+
category: 'a11y',
|
|
144
|
+
default: true,
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
const extRExp = {
|
|
148
|
+
jsx: '\\.[jt]sx?$',
|
|
149
|
+
vue: '\\.vue$',
|
|
150
|
+
svelte: '\\.svelte$',
|
|
151
|
+
astro: '\\.astro',
|
|
152
|
+
pug: '\\.pug$',
|
|
153
|
+
php: '\\.php$',
|
|
154
|
+
erb: '\\.erb$',
|
|
155
|
+
ejs: '\\.ejs$',
|
|
156
|
+
mustache: '\\.(mustache|handlebars)$',
|
|
157
|
+
nunjucks: '\\.nunjucks$',
|
|
158
|
+
liquid: '\\.liquid$',
|
|
159
|
+
};
|
|
160
|
+
export async function initialize() {
|
|
161
|
+
let config = {};
|
|
162
|
+
write(head('Initialization'));
|
|
163
|
+
write.break();
|
|
164
|
+
const langs = await multiSelect({
|
|
165
|
+
message: 'Which do you use template engines?',
|
|
166
|
+
choices: [
|
|
167
|
+
{ name: 'React (JSX)', value: 'jsx' },
|
|
168
|
+
{ name: 'Vue', value: 'vue' },
|
|
169
|
+
{ name: 'Svelte', value: 'svelte' },
|
|
170
|
+
{ name: 'Astro', value: 'astro' },
|
|
171
|
+
{ name: 'Pug', value: 'pug' },
|
|
172
|
+
{ name: 'PHP', value: 'php' },
|
|
173
|
+
{ name: 'eRuby', value: 'erb' },
|
|
174
|
+
{ name: 'EJS', value: 'ejs' },
|
|
175
|
+
{ name: 'Mustache/Handlebars', value: 'mustache' },
|
|
176
|
+
{ name: 'Nunjucks', value: 'nunjucks' },
|
|
177
|
+
{ name: 'liquid (Shopify)', value: 'liquid' },
|
|
178
|
+
],
|
|
179
|
+
});
|
|
180
|
+
const autoInstall = await confirm('May I install them automatically?');
|
|
181
|
+
const customize = await confirm('Do you customize rules?');
|
|
182
|
+
for (const lang of langs) {
|
|
183
|
+
config.parser = config.parser || {};
|
|
184
|
+
// @ts-ignore
|
|
185
|
+
const ext = extRExp[lang];
|
|
186
|
+
if (!ext) {
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
config.parser[ext] = `@markuplint/${lang}-parser`;
|
|
190
|
+
if (lang === 'vue') {
|
|
191
|
+
config = mergeConfig(config, {
|
|
192
|
+
specs: {
|
|
193
|
+
'\\.vue$': '@markuplint/vue-spec',
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
if (lang === 'jsx') {
|
|
198
|
+
config = mergeConfig(config, {
|
|
199
|
+
specs: {
|
|
200
|
+
'\\.[jt]sx?$': '@markuplint/react-spec',
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
if (customize) {
|
|
206
|
+
const ruleNames = Object.keys(defaultRules);
|
|
207
|
+
const categories = Object.keys(ruleCategories);
|
|
208
|
+
const res = await confirmSequence(categories.map(catName => {
|
|
209
|
+
const cat = ruleCategories[catName];
|
|
210
|
+
return {
|
|
211
|
+
message: cat.message,
|
|
212
|
+
name: catName,
|
|
213
|
+
};
|
|
214
|
+
}));
|
|
215
|
+
for (const ruleName of ruleNames) {
|
|
216
|
+
const rule = defaultRules[ruleName];
|
|
217
|
+
if (!rule) {
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
if (res[rule.category]) {
|
|
221
|
+
if (!config.rules) {
|
|
222
|
+
config.rules = {};
|
|
223
|
+
}
|
|
224
|
+
config.rules[ruleName] = rule.recommendedValue || true;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
const recommended = await confirm('Does it import the recommended config?');
|
|
230
|
+
if (recommended) {
|
|
231
|
+
config.extends = [...(config.extends || []), 'markuplint:recommended'];
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
config.rules = {};
|
|
235
|
+
const ruleNames = Object.keys(defaultRules);
|
|
236
|
+
for (const ruleName of ruleNames) {
|
|
237
|
+
const rule = defaultRules[ruleName];
|
|
238
|
+
config.rules[ruleName] = rule.default;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
const filePath = path.resolve(process.cwd(), '.markuplintrc');
|
|
243
|
+
await writeFile(filePath, JSON.stringify(config, null, 2), { encoding: 'utf-8' });
|
|
244
|
+
write(`✨Created: ${filePath}`);
|
|
245
|
+
if (autoInstall) {
|
|
246
|
+
write('Install automatically');
|
|
247
|
+
const modules = ['markuplint', ...langs.map(lang => `@markuplint/${lang}-parser`)];
|
|
248
|
+
if (langs.includes('vue')) {
|
|
249
|
+
modules.push('@markuplint/vue-spec');
|
|
250
|
+
}
|
|
251
|
+
if (langs.includes('jsx')) {
|
|
252
|
+
modules.push('@markuplint/react-spec');
|
|
253
|
+
}
|
|
254
|
+
const result = await installModule(modules, true).catch(e => new Error(e));
|
|
255
|
+
if (result instanceof Error) {
|
|
256
|
+
error.exit();
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
if (result.alreadyExists) {
|
|
260
|
+
write('Modules are installed already.');
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
write('✨ Success');
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { spawnSync } from 'child_process';
|
|
2
|
+
import c from 'cli-color';
|
|
3
|
+
// @ts-ignore
|
|
4
|
+
import detectInstalled from 'detect-installed';
|
|
5
|
+
import hasYarn from 'has-yarn';
|
|
6
|
+
export async function installModule(module, dev = false) {
|
|
7
|
+
module = module.map(m => m.trim());
|
|
8
|
+
const uninstallMods = [];
|
|
9
|
+
try {
|
|
10
|
+
for (const mod of module) {
|
|
11
|
+
const installed = await isInstalled(mod);
|
|
12
|
+
if (!installed) {
|
|
13
|
+
uninstallMods.push(mod);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
catch (_) {
|
|
18
|
+
// void
|
|
19
|
+
}
|
|
20
|
+
if (!uninstallMods.length) {
|
|
21
|
+
return {
|
|
22
|
+
success: true,
|
|
23
|
+
alreadyExists: true,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
const mod = hasYarn() ? 'yarn' : 'npm';
|
|
27
|
+
const installOpt = hasYarn() ? 'add' : 'install';
|
|
28
|
+
const opt = [installOpt];
|
|
29
|
+
if (dev) {
|
|
30
|
+
opt.push('-D');
|
|
31
|
+
}
|
|
32
|
+
opt.push(...uninstallMods);
|
|
33
|
+
return new Promise((resolve, reject) => {
|
|
34
|
+
process.stdout.write(c.blackBright(`${mod} ${opt.join(' ')}\n`));
|
|
35
|
+
const result = spawnSync(mod, opt, { stdio: 'inherit' });
|
|
36
|
+
if (result.error || result.status !== 0) {
|
|
37
|
+
const message = 'Error running command.';
|
|
38
|
+
const error = new Error(message);
|
|
39
|
+
error.stack = message;
|
|
40
|
+
reject(error);
|
|
41
|
+
}
|
|
42
|
+
resolve({
|
|
43
|
+
success: true,
|
|
44
|
+
alreadyExists: false,
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
async function isInstalled(module) {
|
|
49
|
+
return new Promise((resolve, reject) => {
|
|
50
|
+
try {
|
|
51
|
+
detectInstalled(module, {
|
|
52
|
+
local: true,
|
|
53
|
+
}).then((exists) => {
|
|
54
|
+
resolve(exists);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
reject(err);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import stripAnsi from 'strip-ansi';
|
|
2
|
+
import { simpleReporter, standardReporter } from '../reporter/index.mjs';
|
|
3
|
+
export async function output(results, options) {
|
|
4
|
+
const format = options.format ?? 'Standard';
|
|
5
|
+
let out;
|
|
6
|
+
switch (format.toLowerCase()) {
|
|
7
|
+
case 'json': {
|
|
8
|
+
process.stdout.write(JSON.stringify(results.violations, null, 2));
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
case 'simple': {
|
|
12
|
+
out = simpleReporter(results, options);
|
|
13
|
+
break;
|
|
14
|
+
}
|
|
15
|
+
default: {
|
|
16
|
+
out = standardReporter(results, options);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
if (!out.length) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
let msg = `${out.join('\n')}\n`;
|
|
23
|
+
msg = options.color ? msg : stripAnsi(msg);
|
|
24
|
+
// If it has errors, Write to `stderr` and failure and exit.
|
|
25
|
+
if (results.violations.length) {
|
|
26
|
+
process.stderr.write(msg);
|
|
27
|
+
process.exitCode = 1;
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
process.stdout.write(msg);
|
|
31
|
+
}
|