Permalink
Please
sign in to comment.
Browse files
Linting for translators
It looks a little baroque, I know, but the current implementation achieves a few goals I think are interesting: * First of all, it allows --fix * It does this while retaining the processor. Retaining the processor means that vscode and other editors that have eslint support can load translators -- otherwise they would error out on the header. * No temporary files * Monkey-patches notice/notice so it can --fix a missing license comment using information from the header Fix inside editors is not (currently) possible because they must load the plugin processor; eslint [does not support](eslint/eslint#5121) the fix/processor combo, and looking at that discussion, it does not seem it will.
- Loading branch information...
Showing
with
2,024 additions
and 1,209 deletions.
- +11 −0 .ci/.eslintrc
- +22 −0 .ci/AGPL
- +111 −0 .ci/eslint-plugin-zotero-translator/bin/teslint.js
- +7 −181 .ci/eslint-plugin-zotero-translator/index.js
- +15 −0 .ci/eslint-plugin-zotero-translator/lib/processor/index.js
- +30 −0 .ci/eslint-plugin-zotero-translator/lib/rules/header-valid-json.js
- +47 −0 .ci/eslint-plugin-zotero-translator/lib/rules/last-updated.js
- +74 −0 .ci/eslint-plugin-zotero-translator/lib/rules/license.js
- +32 −0 .ci/eslint-plugin-zotero-translator/lib/rules/no-for-each.js
- +35 −0 .ci/eslint-plugin-zotero-translator/lib/rules/not-executable.js
- +32 −0 .ci/eslint-plugin-zotero-translator/lib/rules/prefer-index-of.js
- +30 −0 .ci/eslint-plugin-zotero-translator/lib/rules/test-cases-valid-json.js
- +84 −0 .ci/eslint-plugin-zotero-translator/lib/rules/test-cases.js
- +33 −0 .ci/eslint-plugin-zotero-translator/lib/rules/translator-framework.js
- +69 −0 .ci/eslint-plugin-zotero-translator/lib/rules/translator-id.js
- +75 −0 .ci/eslint-plugin-zotero-translator/lib/rules/translator-type.js
- +249 −0 .ci/eslint-plugin-zotero-translator/lib/translators.js
- +19 −9 .ci/eslint-plugin-zotero-translator/package.json
- +8 −0 .editorconfig
- +22 −17 .eslintrc
- +1,001 −984 package-lock.json
- +18 −18 package.json
@@ -0,0 +1,11 @@ | |||
{ | |||
"env": { | |||
"node": true, | |||
}, | |||
"parserOptions": { | |||
"ecmaVersion": 2018 | |||
}, | |||
"rules": { | |||
"notice/notice": "off" | |||
} | |||
} |
@@ -0,0 +1,22 @@ | |||
/* | |||
***** BEGIN LICENSE BLOCK ***** | |||
|
|||
Copyright © ${ period } ${ holder } | |||
|
|||
This file is part of Zotero. | |||
|
|||
Zotero is free software: you can redistribute it and/or modify | |||
it under the terms of the GNU Affero General Public License as published by | |||
the Free Software Foundation, either version 3 of the License, or | |||
(at your option) any later version. | |||
|
|||
Zotero is distributed in the hope that it will be useful, | |||
but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
GNU Affero General Public License for more details. | |||
|
|||
You should have received a copy of the GNU Affero General Public License | |||
along with Zotero. If not, see <http://www.gnu.org/licenses/>. | |||
|
|||
***** END LICENSE BLOCK ***** | |||
*/ |
@@ -0,0 +1,111 @@ | |||
#!/usr/bin/env node | |||
|
|||
'use strict'; | |||
|
|||
const fs = require('fs'); | |||
const path = require('path'); | |||
const find = require('recursive-readdir-synchronous'); | |||
const CLIEngine = require("eslint").CLIEngine; | |||
const argv = require('commander'); | |||
|
|||
const translators = require('../lib/translators'); | |||
|
|||
argv | |||
.version(CLIEngine.version) | |||
.option('-f, --fix', 'Automatically fix problems') | |||
.option('--no-ignore', 'Disable use of ignore files and patterns') | |||
.option('--quiet', 'Report errors only - default: false') | |||
.option('--dump-decorated [file]', 'Dump decorated translator to file for inspection') | |||
.parse(process.argv); | |||
|
|||
/* PATCHES */ | |||
// disable the processor so that fixing works | |||
const eslintPluginZoteroTranslator = require('eslint-plugin-zotero-translator'); | |||
delete eslintPluginZoteroTranslator.processors; | |||
|
|||
/* MAIN */ | |||
// split sources to lint into regular javascript (handled by executeOnFiles) and translators (handled by executeOnText) | |||
const sources = { | |||
javascripts: [], | |||
translators: [], | |||
errors: 0, | |||
}; | |||
function findIgnore(file, stats) { | |||
if (stats.isDirectory()) return (path.basename(file) == "node_modules"); | |||
return !file.endsWith('.js'); | |||
} | |||
for (const target of argv.args) { | |||
if (!fs.existsSync(target)) continue; | |||
const files = fs.lstatSync(target).isDirectory() ? find(target, [findIgnore]) : [target]; | |||
for (const file of files) { | |||
if (path.dirname(path.resolve(file)) === translators.cache.repo) { | |||
const translator = translators.cache.get(file); | |||
if (translator.header) { | |||
translator.filename = file; | |||
sources.translators.push(translator); | |||
} | |||
else { | |||
sources.javascripts.push(file); | |||
} | |||
} | |||
else { | |||
sources.javascripts.push(file); | |||
} | |||
} | |||
} | |||
|
|||
const cli = new CLIEngine({ | |||
cwd: translators.cache.repo, | |||
fix: argv.fix, | |||
ignore: argv.ignore, // otherwise you can't lint stuff in hidden dirs | |||
}); | |||
const formatter = cli.getFormatter(); | |||
function showResults(files, results) { | |||
if (argv.quiet) results = CLIEngine.getErrorResults(results); | |||
for (const res of results) { | |||
sources.errors += res.errorCount; | |||
} | |||
|
|||
if (results.length) { | |||
console.log(formatter(results)); // eslint-disable-line no-console | |||
} | |||
else { | |||
if (Array.isArray(files)) files = files.join(', '); | |||
if (!argv.quiet) console.log(files, 'OK'); // eslint-disable-line no-console | |||
} | |||
} | |||
|
|||
if (sources.javascripts.length) { | |||
const report = cli.executeOnFiles(sources.javascripts); | |||
if (argv.fix) { | |||
for (const result of report.results) { | |||
if (result.messages.find(msg => msg.ruleId === 'notice/notice' && msg.fix)) { | |||
console.log(`Not safe to apply 'notice/notice' to ${result.filePath}`); // eslint-disable-line no-console | |||
process.exit(1); // eslint-disable-line no-process-exit | |||
} | |||
} | |||
CLIEngine.outputFixes(report); | |||
} | |||
showResults(sources.javascripts, report.results); | |||
} | |||
|
|||
for (const translator of sources.translators) { | |||
if (argv.dumpDecorated) fs.writeFileSync(argv.dumpDecorated, translator.source, 'utf-8'); | |||
const report = cli.executeOnText(translator.source, translator.filename); | |||
if (argv.fix) { | |||
for (const result of report.results) { | |||
if (result.output) { | |||
try { | |||
fs.writeFileSync(result.filePath, translators.strip(result.output), 'utf-8'); | |||
} | |||
catch (err) { | |||
console.log(`Error writing fixed ${result.filePath}: ${err.message}`); // eslint-disable-line no-console | |||
process.exit(1); // eslint-disable-line no-process-exit | |||
} | |||
} | |||
} | |||
} | |||
showResults(translator.filename, report.results); | |||
} | |||
|
|||
process.exit(sources.errors); // eslint-disable-line no-process-exit |
@@ -0,0 +1,15 @@ | |||
'use strict'; | |||
|
|||
const translators = require('../translators').cache; | |||
|
|||
module.exports = { | |||
preprocess: function (text, filename) { | |||
const translator = translators.get(filename); | |||
|
|||
return [(typeof translator.source === 'string') ? translator.source : text]; | |||
}, | |||
|
|||
postprocess: function (messages, _filename) { | |||
return messages[0]; | |||
}, | |||
}; |
@@ -0,0 +1,30 @@ | |||
'use strict'; | |||
|
|||
const translators = require('../translators').cache; | |||
|
|||
module.exports = { | |||
meta: { | |||
type: 'problem', | |||
docs: { | |||
description: 'disallow invalid JSON in header', | |||
category: 'Possible Errors', | |||
}, | |||
}, | |||
|
|||
create: function (context) { | |||
return { | |||
Program: function (_node) { | |||
const translator = translators.get(context.getFilename()); | |||
|
|||
if (!translator.source) return; // regular js source | |||
|
|||
if (translator.header.error) { | |||
context.report({ | |||
message: `Could not parse header: ${translator.header.error.message}`, | |||
loc: { start: { line: translator.error.line, column: translator.error.column } }, | |||
}); | |||
} | |||
} | |||
}; | |||
}, | |||
}; |
Oops, something went wrong.
0 comments on commit
9427b33