diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:54:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:54:43 +0000 |
commit | e4283f6d48b98e764b988b43bbc86b9d52e6ec94 (patch) | |
tree | c8f7f7a6c2f5faa2942d27cefc6fd46cca492656 /.gitlab-ci | |
parent | Initial commit. (diff) | |
download | gnome-shell-e4283f6d48b98e764b988b43bbc86b9d52e6ec94.tar.xz gnome-shell-e4283f6d48b98e764b988b43bbc86b9d52e6ec94.zip |
Adding upstream version 43.9.upstream/43.9upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | .gitlab-ci.yml | 289 | ||||
-rw-r--r-- | .gitlab-ci/check-potfiles.js | 207 | ||||
-rwxr-xr-x | .gitlab-ci/check-potfiles.sh | 38 | ||||
-rwxr-xr-x | .gitlab-ci/checkout-mutter.sh | 70 | ||||
-rw-r--r-- | .gitlab-ci/commit-rules.yml | 16 | ||||
-rwxr-xr-x | .gitlab-ci/download-coverity-tarball.sh | 38 | ||||
-rwxr-xr-x | .gitlab-ci/install-meson-project.sh | 82 | ||||
-rwxr-xr-x | .gitlab-ci/run-eslint | 128 |
8 files changed, 868 insertions, 0 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..468549f --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,289 @@ +include: + - remote: 'https://gitlab.gnome.org/GNOME/citemplates/raw/HEAD/flatpak/flatpak_ci_initiative.yml' + - remote: 'https://gitlab.freedesktop.org/freedesktop/ci-templates/-/raw/34f4ade99434043f88e164933f570301fd18b125/templates/fedora.yml' + - remote: 'https://gitlab.freedesktop.org/freedesktop/ci-templates/-/raw/34f4ade99434043f88e164933f570301fd18b125/templates/ci-fairy.yml' + +stages: + - pre_review + - prep + - review + - build + - test + - analyze + - deploy + +default: + image: registry.gitlab.gnome.org/gnome/mutter/fedora/36:x86_64-2022-09-01.0 + # Cancel jobs if newer commits are pushed to the branch + interruptible: true + # Auto-retry jobs in case of infra failures + retry: + max: 1 + when: + - 'runner_system_failure' + - 'stuck_or_timeout_failure' + - 'scheduler_failure' + - 'api_failure' + +variables: + FDO_UPSTREAM_REPO: GNOME/gnome-shell + BUNDLE: "extensions-git.flatpak" + JS_LOG: "js-report.txt" + LINT_LOG: "eslint-report.xml" + LINT_MR_LOG: "eslint-mr-report.xml" + +workflow: + rules: + - if: '$CI_MERGE_REQUEST_IID' + - if: '$CI_COMMIT_TAG' + - if: '$CI_COMMIT_BRANCH' + +.pipeline_guard: &pipeline_guard + rules: + - if: '$CI_PIPELINE_SOURCE == "merge_request_event"' + - if: '$CI_COMMIT_TAG' + - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH' + - if: '$CI_COMMIT_BRANCH =~ /^gnome-[0-9-]+$/' + - when: 'manual' + +.gnome-shell.fedora:35: + variables: + FDO_DISTRIBUTION_VERSION: 35 + FDO_DISTRIBUTION_TAG: '2022-01-18.0' + FDO_DISTRIBUTION_PACKAGES: + findutils + mozjs91-devel + nodejs + npm + meson + pkgconfig(gio-2.0) + pkgconfig(gio-unix-2.0) + pkgconfig(gnome-autoar-0) + pkgconfig(json-glib-1.0) + FDO_DISTRIBUTION_EXEC: | + # For static analysis with eslint + npm install -g eslint eslint-plugin-jsdoc && + + dnf group install -y 'Development Tools' \ + 'C Development Tools and Libraries' && + + ./.gitlab-ci/install-meson-project.sh \ + --subdir subprojects/extensions-tool/ \ + --prepare ./generate-translations.sh \ + -Dman=false \ + https://gitlab.gnome.org/GNOME/gnome-shell.git \ + main && + + dnf clean all + +check_commit_log: + extends: + - .fdo.ci-fairy + stage: pre_review + variables: + GIT_DEPTH: "100" + script: + - if [[ x"$CI_MERGE_REQUEST_TARGET_BRANCH_NAME" != "x" ]] ; + then + ci-fairy check-commits --junit-xml=commit-message-junit-report.xml ; + else + echo "Not a merge request" ; + fi + <<: *pipeline_guard + artifacts: + expire_in: 1 week + paths: + - commit-message-junit-report.xml + reports: + junit: commit-message-junit-report.xml + +check-merge-request: + extends: + - .fdo.ci-fairy + stage: pre_review + script: + - if [[ x"$CI_MERGE_REQUEST_TARGET_BRANCH_NAME" != "x" ]] ; + then + ci-fairy check-merge-request --require-allow-collaboration --junit-xml=check-merge-request-report.xml ; + else + echo "Not a merge request" ; + fi + <<: *pipeline_guard + artifacts: + expire_in: 1 week + paths: + - check-merge-request-report.xml + reports: + junit: check-merge-request-report.xml + +build-fedora-container: + extends: + - .fdo.container-build@fedora@x86_64 + - .gnome-shell.fedora:35 + stage: prep + +js_check: + extends: + - .fdo.distribution-image@fedora + - .gnome-shell.fedora:35 + stage: review + script: + - find js -name '*.js' $(printf "! -wholename %s " $(cat .jscheckignore)) -exec js91 -c '{}' ';' 2>&1 | tee $JS_LOG + - (! grep -q . $JS_LOG) + artifacts: + paths: + - ${JS_LOG} + when: on_failure + +eslint: + extends: + - .fdo.distribution-image@fedora + - .gnome-shell.fedora:35 + stage: review + script: + - export NODE_PATH=$(npm root -g) + - ./.gitlab-ci/run-eslint --output-file ${LINT_LOG} --format junit + artifacts: + reports: + junit: ${LINT_LOG} + when: always + +eslint_mr: + extends: + - .fdo.distribution-image@fedora + - .gnome-shell.fedora:35 + stage: review + script: + - export NODE_PATH=$(npm root -g) + - ./.gitlab-ci/run-eslint --output-file ${LINT_MR_LOG} --format junit + --remote ${CI_MERGE_REQUEST_PROJECT_URL}.git + --branch ${CI_MERGE_REQUEST_TARGET_BRANCH_NAME} + only: + - merge_requests + artifacts: + reports: + junit: ${LINT_MR_LOG} + when: always + +potfile_c_check: + extends: + - .fdo.distribution-image@fedora + - .gnome-shell.fedora:35 + stage: review + script: + - ./.gitlab-ci/check-potfiles.sh + +potfile_js_check: + extends: + - .fdo.distribution-image@fedora + - .gnome-shell.fedora:35 + stage: review + script: + - js91 -m .gitlab-ci/check-potfiles.js + +build: + stage: build + needs: ["check_commit_log"] + variables: + GIT_SUBMODULE_STRATEGY: normal + before_script: + - .gitlab-ci/checkout-mutter.sh + - meson mutter mutter/build --prefix=/usr + - meson install -C mutter/build + script: + - meson . build -Dbuildtype=debugoptimized -Dman=false --werror + - meson compile -C build + - meson install -C build + artifacts: + expire_in: 1 day + paths: + - mutter + - build + +test: + stage: test + needs: ["build"] + variables: + GIT_SUBMODULE_STRATEGY: normal + XDG_RUNTIME_DIR: "$CI_PROJECT_DIR/runtime-dir" + NO_AT_BRIDGE: "1" + before_script: + - meson install -C mutter/build + script: + - dbus-run-session -- xvfb-run meson test -C build --no-rebuild + artifacts: + expire_in: 1 day + paths: + - build/meson-logs/testlog.txt + reports: + junit: build/meson-logs/testlog.junit.xml + when: on_failure + +test-coverity: + rules: + - if: '$CI_PIPELINE_SOURCE == "schedule" && $GNOME_SHELL_SCHEDULED_JOB == "coverity"' + when: always + - when: manual + needs: ["build"] + stage: analyze + allow_failure: true + variables: + GIT_SUBMODULE_STRATEGY: normal + before_script: + - meson install -C mutter/build + script: + - .gitlab-ci/download-coverity-tarball.sh + - CC=clang meson coverity-build -Dman=false + - ./coverity/cov-analysis-linux64-*/bin/cov-build --fs-capture-search js --dir cov-int meson compile -C coverity-build + - tar czf cov-int.tar.gz cov-int + - curl https://scan.coverity.com/builds?project=GNOME+Shell + --form token=$COVERITY_TOKEN --form email=carlosg@gnome.org + --form file=@cov-int.tar.gz --form version="`git describe --tags`" + --form description="GitLab CI build" + cache: + key: coverity-tarball + paths: + - coverity + +flatpak: + stage: build + needs: ["check_commit_log"] + variables: + SUBPROJECT: "subprojects/extensions-app" + # Your manifest path + MANIFEST_PATH: "$SUBPROJECT/build-aux/flatpak/org.gnome.Extensions.json" + RUNTIME_REPO: "https://nightly.gnome.org/gnome-nightly.flatpakrepo" + FLATPAK_MODULE: "gnome-extensions-app" + APP_ID: "org.gnome.Extensions.Devel" + extends: .flatpak + +nightly: + extends: '.publish_nightly' + +dist: + variables: + XDG_RUNTIME_DIR: "$CI_PROJECT_DIR/runtime-dir" + NO_AT_BRIDGE: "1" + GIT_SUBMODULE_STRATEGY: normal + stage: deploy + needs: ["build"] + before_script: + - meson install -C mutter/build + - mkdir -m 700 $XDG_RUNTIME_DIR + script: + - dbus-run-session xvfb-run meson dist -C build + rules: + - if: '$CI_PIPELINE_SOURCE == "merge_request_event"' + changes: + - "**/meson.build" + - meson/* + + +dist-tarball: + extends: dist + artifacts: + expose_as: 'Get tarball here' + paths: + - build/meson-dist/$CI_PROJECT_NAME-$CI_COMMIT_TAG.tar.xz + rules: + - if: '$CI_COMMIT_TAG' diff --git a/.gitlab-ci/check-potfiles.js b/.gitlab-ci/check-potfiles.js new file mode 100644 index 0000000..0c8885e --- /dev/null +++ b/.gitlab-ci/check-potfiles.js @@ -0,0 +1,207 @@ +const gettextFuncs = new Set([ + '_', + 'N_', + 'C_', + 'NC_', + 'dcgettext', + 'dgettext', + 'dngettext', + 'dpgettext', + 'gettext', + 'ngettext', + 'pgettext', +]); + +function dirname(file) { + const split = file.split('/'); + split.pop(); + return split.join('/'); +} + +const scriptDir = dirname(import.meta.url); +const root = dirname(scriptDir); + +const excludedFiles = new Set(); +const foundFiles = new Set() + +function addExcludes(filename) { + const contents = os.file.readFile(filename); + const lines = contents.split('\n') + .filter(l => l && !l.startsWith('#')); + lines.forEach(line => excludedFiles.add(line)); +} + +addExcludes(`${root}/po/POTFILES.in`); +addExcludes(`${root}/po/POTFILES.skip`); + +function walkAst(node, func) { + func(node); + nodesToWalk(node).forEach(n => walkAst(n, func)); +} + +function findGettextCalls(node) { + switch(node.type) { + case 'CallExpression': + if (node.callee.type === 'Identifier' && + gettextFuncs.has(node.callee.name)) + throw new Error(); + if (node.callee.type === 'MemberExpression' && + node.callee.object.type === 'Identifier' && + node.callee.object.name === 'Gettext' && + node.callee.property.type === 'Identifier' && + gettextFuncs.has(node.callee.property.name)) + throw new Error(); + break; + } + return true; +} + +function nodesToWalk(node) { + switch(node.type) { + case 'ArrayPattern': + case 'BreakStatement': + case 'CallSiteObject': // i.e. strings passed to template + case 'ContinueStatement': + case 'DebuggerStatement': + case 'EmptyStatement': + case 'Identifier': + case 'Literal': + case 'MetaProperty': // i.e. new.target + case 'Super': + case 'ThisExpression': + return []; + case 'ArrowFunctionExpression': + case 'FunctionDeclaration': + case 'FunctionExpression': + return [...node.defaults, node.body].filter(n => !!n); + case 'AssignmentExpression': + case 'BinaryExpression': + case 'ComprehensionBlock': + case 'LogicalExpression': + return [node.left, node.right]; + case 'ArrayExpression': + case 'TemplateLiteral': + return node.elements.filter(n => !!n); + case 'BlockStatement': + case 'Program': + return node.body; + case 'StaticClassBlock': + return [node.body]; + case 'ClassField': + return [node.name, node.init]; + case 'CallExpression': + case 'NewExpression': + case 'OptionalCallExpression': + case 'TaggedTemplate': + return [node.callee, ...node.arguments]; + case 'CatchClause': + return [node.body, node.guard].filter(n => !!n); + case 'ClassExpression': + case 'ClassStatement': + return [...node.body, node.superClass].filter(n => !!n); + case 'ClassMethod': + return [node.name, node.body]; + case 'ComprehensionExpression': + case 'GeneratorExpression': + return [node.body, ...node.blocks, node.filter].filter(n => !!n); + case 'ComprehensionIf': + return [node.test]; + case 'ComputedName': + return [node.name]; + case 'ConditionalExpression': + case 'IfStatement': + return [node.test, node.consequent, node.alternate].filter(n => !!n); + case 'DoWhileStatement': + case 'WhileStatement': + return [node.body, node.test]; + case 'ExportDeclaration': + return [node.declaration, node.source].filter(n => !!n); + case 'ImportDeclaration': + return [...node.specifiers, node.source]; + case 'LetStatement': + return [...node.head, node.body]; + case 'ExpressionStatement': + return [node.expression]; + case 'ForInStatement': + case 'ForOfStatement': + return [node.body, node.left, node.right]; + case 'ForStatement': + return [node.init, node.test, node.update, node.body].filter(n => !!n); + case 'LabeledStatement': + return [node.body]; + case 'MemberExpression': + return [node.object, node.property]; + case 'ObjectExpression': + case 'ObjectPattern': + return node.properties; + case 'OptionalExpression': + return [node.expression]; + case 'OptionalMemberExpression': + return [node.object, node.property]; + case 'Property': + case 'PrototypeMutation': + return [node.value]; + case 'ReturnStatement': + case 'ThrowStatement': + case 'UnaryExpression': + case 'UpdateExpression': + case 'YieldExpression': + return node.argument ? [node.argument] : []; + case 'SequenceExpression': + return node.expressions; + case 'SpreadExpression': + return [node.expression]; + case 'SwitchCase': + return [node.test, ...node.consequent].filter(n => !!n); + case 'SwitchStatement': + return [node.discriminant, ...node.cases]; + case 'TryStatement': + return [node.block, node.handler, node.finalizer].filter(n => !!n); + case 'VariableDeclaration': + return node.declarations; + case 'VariableDeclarator': + return node.init ? [node.init] : []; + case 'WithStatement': + return [node.object, node.body]; + default: + print(`Ignoring ${node.type}, you should probably fix this in the script`); + } +} + +function walkDir(dir) { + os.file.listDir(dir).forEach(child => { + if (child.startsWith('.')) + return; + + const path = os.path.join(dir, child); + const relativePath = path.replace(`${root}/`, ''); + if (excludedFiles.has(relativePath)) + return; + + if (!child.endsWith('.js')) { + try { + walkDir(path); + } catch (e) { + // not a directory + } + return; + } + + try { + const script = os.file.readFile(path); + const ast = Reflect.parse(script); + walkAst(ast, findGettextCalls); + } catch (e) { + foundFiles.add(path); + } + }); +} + +walkDir(root); + +if (foundFiles.size === 0) + quit(0); + +print('The following files are missing from po/POTFILES.in:') +foundFiles.forEach(f => print(` ${f}`)); +quit(1); diff --git a/.gitlab-ci/check-potfiles.sh b/.gitlab-ci/check-potfiles.sh new file mode 100755 index 0000000..0969da1 --- /dev/null +++ b/.gitlab-ci/check-potfiles.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash + +srcdirs="src subprojects/extensions-tool" +uidirs="js subprojects/extensions-app" +desktopdirs="data subprojects/extensions-app/ subprojects/extensions-tool" + +# find source files that contain gettext keywords +files=$(grep -lR --include='*.c' '\(gettext\|[^I_)]_\)(' $srcdirs) + +# find ui files that contain translatable string +files="$files "$(grep -lRi --include='*.ui' 'translatable="[ty1]' $uidirs) + +# find .desktop files +files="$files "$(find $desktopdirs -name '*.desktop*') + +# filter out excluded files +if [ -f po/POTFILES.skip ]; then + files=$(for f in $files; do ! grep -q ^$f po/POTFILES.skip && echo $f; done) +fi + +# find those that aren't listed in POTFILES.in +missing=$(for f in $files; do ! grep -q ^$f po/POTFILES.in && echo $f; done) + +if [ ${#missing} -eq 0 ]; then + exit 0 +fi + +cat >&2 <<EOT + +The following files are missing from po/POTFILES.po: + +EOT +for f in $missing; do + echo " $f" >&2 +done +echo >&2 + +exit 1 diff --git a/.gitlab-ci/checkout-mutter.sh b/.gitlab-ci/checkout-mutter.sh new file mode 100755 index 0000000..76375fd --- /dev/null +++ b/.gitlab-ci/checkout-mutter.sh @@ -0,0 +1,70 @@ +#!/usr/bin/bash + +fetch() { + local remote=$1 + local ref=$2 + + git fetch --quiet --depth=1 $remote $ref 2>/dev/null +} + +mutter_target= + +echo -n Cloning into mutter ... +if git clone --quiet --depth=1 https://gitlab.gnome.org/GNOME/mutter.git; then + echo \ done +else + echo \ failed + exit 1 +fi + +cd mutter + +if [ "$CI_MERGE_REQUEST_TARGET_BRANCH_NAME" ]; then + merge_request_remote=${CI_MERGE_REQUEST_SOURCE_PROJECT_URL//gnome-shell/mutter} + merge_request_branch=$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME + + echo -n Looking for $merge_request_branch on remote ... + if fetch $merge_request_remote $merge_request_branch; then + echo \ found + mutter_target=FETCH_HEAD + else + echo \ not found + + echo -n Looking for $CI_MERGE_REQUEST_TARGET_BRANCH_NAME instead ... + if fetch origin $CI_MERGE_REQUEST_TARGET_BRANCH_NAME; then + echo \ found + mutter_target=FETCH_HEAD + else + echo \ not found + fi + fi +fi + +if [ -z "$mutter_target" ]; then + ref_remote=${CI_PROJECT_URL//gnome-shell/mutter} + echo -n Looking for $CI_COMMIT_REF_NAME on remote ... + if fetch $ref_remote $CI_COMMIT_REF_NAME; then + echo \ found + mutter_target=FETCH_HEAD + else + echo \ not found + fi +fi + +fallback_branch=${CI_COMMIT_TAG:+gnome-}${CI_COMMIT_TAG%%.*} +if [ -z "$mutter_target" -a "$fallback_branch" ]; then + echo -n Looking for $fallback_branch instead ... + if fetch origin $fallback_branch; then + echo \ found + mutter_target=FETCH_HEAD + else + echo \ not found + fi +fi + +if [ -z "$mutter_target" ]; then + mutter_target=HEAD + echo Using $mutter_target instead +fi + +git checkout -q $mutter_target diff --git a/.gitlab-ci/commit-rules.yml b/.gitlab-ci/commit-rules.yml new file mode 100644 index 0000000..5828f8a --- /dev/null +++ b/.gitlab-ci/commit-rules.yml @@ -0,0 +1,16 @@ +patterns: + deny: + - regex: '^$CI_MERGE_REQUEST_PROJECT_URL/(-/)?merge_requests/$CI_MERGE_REQUEST_IID$' + message: Commit message must not contain a link to its own merge request + - regex: '^(st-|St)' + message: Commit message subject should not be prefixed with 'st-' or 'St', use 'st/' instead + where: subject + - regex: '^[^:]+: [a-z]' + message: "Commit message subject should be properly Capitalized. E.g. 'window: Marginalize extradicity'" + where: subject + - regex: '^\S*\.(js|c|h):' + message: Commit message subject prefix should not include .c, .h etc. + where: subject + - regex: '([^.]\.|[:,;])\s*$' + message: Commit message subject should not end with punctuation + where: subject diff --git a/.gitlab-ci/download-coverity-tarball.sh b/.gitlab-ci/download-coverity-tarball.sh new file mode 100755 index 0000000..e2afc5d --- /dev/null +++ b/.gitlab-ci/download-coverity-tarball.sh @@ -0,0 +1,38 @@ +#!/usr/bin/bash + +# We need a coverity token to fetch the tarball +if [ -x $COVERITY_TOKEN ] +then + echo "No coverity token. Run this job from a protected branch." + exit -1 +fi + +mkdir -p coverity + +# Download and check MD5 first +curl https://scan.coverity.com/download/linux64 \ + --data "token=$COVERITY_TOKEN&project=GNOME+Shell&md5=1" \ + --output /tmp/coverity_tool.md5 + +diff /tmp/coverity_tool.md5 coverity/coverity_tool.md5 >/dev/null 2>&1 + +if [ $? -eq 0 -a -d coverity/cov-analysis* ] +then + echo "Coverity tarball is up-to-date" + exit 0 +fi + +# Download and extract coverity tarball +curl https://scan.coverity.com/download/linux64 \ + --data "token=$COVERITY_TOKEN&project=GNOME+Shell" \ + --output /tmp/coverity_tool.tgz + +rm -rf ./coverity/cov-analysis* + +tar zxf /tmp/coverity_tool.tgz -C coverity/ +if [ $? -eq 0 ] +then + mv /tmp/coverity_tool.md5 coverity/ +fi + +rm /tmp/coverity_tool.tgz diff --git a/.gitlab-ci/install-meson-project.sh b/.gitlab-ci/install-meson-project.sh new file mode 100755 index 0000000..8ecf8a3 --- /dev/null +++ b/.gitlab-ci/install-meson-project.sh @@ -0,0 +1,82 @@ +#!/bin/bash + +set -e + +usage() { + cat <<-EOF + Usage: $(basename $0) [OPTION…] REPO_URL COMMIT + + Check out and install a meson project + + Options: + -Dkey=val Option to pass on to meson + --subdir Build subdirectory instead of whole project + --prepare Script to run before build + + -h, --help Display this help + + EOF +} + +TEMP=$(getopt \ + --name=$(basename $0) \ + --options='D:h' \ + --longoptions='subdir:' \ + --longoptions='prepare:' \ + --longoptions='help' \ + -- "$@") + +eval set -- "$TEMP" +unset TEMP + +MESON_OPTIONS=() +SUBDIR=. +PREPARE=: + +while true; do + case "$1" in + -D) + MESON_OPTIONS+=( -D$2 ) + shift 2 + ;; + + --subdir) + SUBDIR=$2 + shift 2 + ;; + + --prepare) + PREPARE=$2 + shift 2 + ;; + + -h|--help) + usage + exit 0 + ;; + + --) + shift + break + ;; + esac +done + +if [[ $# -lt 2 ]]; then + usage + exit 1 +fi + +REPO_URL="$1" +COMMIT="$2" + +CHECKOUT_DIR=$(mktemp --directory) +trap "rm -rf $CHECKOUT_DIR" EXIT + +git clone --depth 1 "$REPO_URL" -b "$COMMIT" "$CHECKOUT_DIR" + +pushd "$CHECKOUT_DIR/$SUBDIR" +sh -c "$PREPARE" +meson setup --prefix=/usr _build "${MESON_OPTIONS[@]}" +meson install -C _build +popd diff --git a/.gitlab-ci/run-eslint b/.gitlab-ci/run-eslint new file mode 100755 index 0000000..2a8f60d --- /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(getOption('--format', '-f')); + 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); +}); |