[gnome-shell] ci: Use node-js script for running eslint



commit 8c13e3855e0adae812d812aa05e7b3d28d25160b
Author: Florian Müllner <fmuellner gnome org>
Date:   Sat Nov 7 07:11:09 2020 +0100

    ci: Use node-js script for running eslint
    
    This is unnecessary hard in shell when compared to a proper programming
    language. It becomes even easier with a node-js script, as that gives us
    access to the underlying ESLint module rather than just the CLI interface.
    
    Besides that, node-js has the added benefit that we don't need to add
    more dependencies to the CI image.
    
    https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1497

 .gitlab-ci.yml           |   6 ++-
 .gitlab-ci/run-eslint    | 128 +++++++++++++++++++++++++++++++++++++++++++++++
 .gitlab-ci/run-eslint.sh | 116 ------------------------------------------
 3 files changed, 132 insertions(+), 118 deletions(-)
---
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index adad8f958d..52e60764d7 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -9,6 +9,7 @@ stages:
 variables:
     BUNDLE: "extensions-git.flatpak"
     JS_LOG: "js-report.txt"
+    LINT_LOG: "eslint-report.txt"
 
 .only_default: &only_default
     only:
@@ -42,11 +43,12 @@ eslint:
     image: registry.gitlab.gnome.org/gnome/gnome-shell/extension-ci:v2
     stage: review
     script:
-        - ./.gitlab-ci/run-eslint.sh
+        - export NODE_PATH=$(npm root -g)
+        - ./.gitlab-ci/run-eslint --output-file ${LINT_LOG}
     <<: *only_default
     artifacts:
         paths:
-            - reports
+            - ${LINT_LOG}
         when: always
 
 potfile_check:
diff --git a/.gitlab-ci/run-eslint b/.gitlab-ci/run-eslint
new file mode 100755
index 0000000000..68a664109f
--- /dev/null
+++ b/.gitlab-ci/run-eslint
@@ -0,0 +1,128 @@
+#!/usr/bin/env node
+
+const { ESLint } = require('eslint');
+const fs = require('fs');
+const path = require('path');
+const { spawn } = require('child_process');
+
+function createConfig(config) {
+    const options = {
+        cache: true,
+        cacheLocation: `.eslintcache-${config}`,
+    };
+
+    if (config === 'legacy')
+        options.overrideConfigFile='lint/eslintrc-legacy.yml';
+
+    return new ESLint(options);
+}
+
+function git(...args) {
+    const git = spawn('git', args, { stdio: ['ignore', null, 'ignore'] });
+    git.stdout.setEncoding('utf8');
+
+    return new Promise(resolve => {
+        let out = '';
+        git.stdout.on('data', chunk => out += chunk);
+        git.stdout.on('end', () => resolve(out.trim()));
+    });
+}
+
+function createCommon(report1, report2, ignoreColumn=false) {
+    return report1.map(result => {
+        const { filePath, messages } = result;
+        const match =
+            report2.find(r => r.filePath === filePath) || { messages: [] };
+
+        const filteredMessages = messages.filter(
+            msg => match.messages.some(
+                m => m.line === msg.line && (ignoreColumn || m.column === msg.column)));
+
+        const [errorCount, warningCount] = filteredMessages.reduce(
+            ([e, w], msg) => {
+                return [
+                    e + Number(msg.severity === 2),
+                    w + Number(msg.severity === 1)];
+            }, [0, 0]);
+
+        return {
+            filePath,
+            messages: filteredMessages,
+            errorCount,
+            warningCount,
+        };
+    });
+}
+
+async function getMergeRequestChanges(remote, branch) {
+    await git('fetch', remote, branch);
+    const branchPoint = await git('merge-base', 'HEAD', 'FETCH_HEAD');
+    const diff = await git('diff', '-U0', `${branchPoint}...HEAD`);
+
+    const report = [];
+    let messages = null;
+    for (const line of diff.split('\n')) {
+        if (line.startsWith('+++ b/')) {
+            const filePath = path.resolve(line.substring(6));
+            messages = filePath.endsWith('.js') ? [] : null;
+            if (messages)
+                report.push({ filePath, messages });
+        } else if (messages && line.startsWith('@@ ')) {
+            [, , changes] = line.split(' ');
+            [start, count] = `${changes},1`.split(',').map(i => parseInt(i));
+            for (let i = start; i < start + count; i++)
+                messages.push({ line: i });
+        }
+    }
+
+    return report;
+}
+
+function getOption(...names) {
+    const optIndex =
+        process.argv.findIndex(arg => names.includes(arg)) + 1;
+
+    if (optIndex === 0)
+        return undefined;
+
+    return process.argv[optIndex];
+}
+
+(async function main() {
+    const outputOption = getOption('--output-file', '-o');
+    const outputPath = outputOption ? path.resolve(outputOption) : null;
+
+    const sourceDir = path.dirname(process.argv[1]);
+    process.chdir(path.resolve(sourceDir, '..'));
+
+    const remote = getOption('--remote') || 'origin';
+    const branch = getOption('--branch', '-b');
+
+    const sources = ['js', 'subprojects/extensions-app/js'];
+    const regular = createConfig('regular');
+
+    const ops = [];
+    ops.push(regular.lintFiles(sources));
+    if (branch)
+        ops.push(getMergeRequestChanges(remote, branch));
+    else
+        ops.push(createConfig('legacy').lintFiles(sources));
+
+    const results = await Promise.all(ops);
+    const commonResults = createCommon(...results, branch !== undefined);
+
+    const formatter = await regular.loadFormatter();
+    const resultText = formatter.format(commonResults);
+
+    if (outputPath) {
+        fs.mkdirSync(path.dirname(outputPath), { recursive: true });
+        fs.writeFileSync(outputPath, resultText);
+    } else {
+        console.log(resultText);
+    }
+
+    process.exitCode = commonResults.some(r => r.errorCount > 0) ? 1 : 0;
+})().catch((error) => {
+    process.exitCode = 1;
+    console.error(error);
+});


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]