diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dccead528..617bb0d4f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,16 +4,22 @@ name: ci on: push: branches: + # - '*/*/*' - alpha - beta - master - sandbox workflow_dispatch: branches: + # - '*/*/*' - alpha - beta - master - sandbox +env: + CI_MAIN_JOB: node.v16.x64.linux + GITHUB_ARTIFACT_UPLOAD_NODE_VERSION: v16 + # MY_GITHUB_TOKEN: ${{ secrets.MY_GITHUB_TOKEN }} jobs: @@ -33,25 +39,38 @@ jobs: - macos-latest - ubuntu-latest - windows-latest - name: job1 . node . v${{ matrix.node_version }} . ${{ matrix.architecture }} . ${{ matrix.os }} + name: | + job1 + node + v${{ matrix.node_version }} + ${{ matrix.architecture }} + ${{ matrix.os }} runs-on: ${{ matrix.os }} steps: # disable autocrlf in windows - run: git config --global core.autocrlf false # https://github.com/actions/checkout - uses: actions/checkout@v2 + # fetch jslint_ci.sh from trusted source + - run: | + git fetch origin alpha + git checkout origin/alpha .ci.sh jslint_ci.sh + # pre-run .ci.sh + - run: sh jslint_ci.sh shCiPre # https://github.com/actions/cache - uses: actions/cache@v2 with: - key: ${{ matrix.architecture }}-${{ matrix.node_version }}-${{ matrix.os }}-${{ hashFiles('./package.json') }} + key: | + ${{ matrix.architecture }} + ${{ matrix.node_version }} + ${{ matrix.os }} + ${{ hashFiles('./package.json') }} path: .github_cache # https://github.com/actions/setup-node - uses: actions/setup-node@v2 with: node-version: ${{ matrix.node_version }} architecture: ${{ matrix.architecture }} - # fetch jslint_ci.sh from trusted source - - run: git fetch origin alpha && git checkout origin/alpha .ci.sh jslint_ci.sh # run nodejs coverages and tests - run: sh jslint_ci.sh shCiBase # shCiBase - end @@ -59,10 +78,9 @@ jobs: # shCiArtifactUpload - start # fetch jslint_ci.sh from trusted source - - run: git fetch origin alpha && git checkout origin/alpha .ci.sh jslint_ci.sh + - run: | + git fetch origin alpha + git checkout origin/alpha .ci.sh jslint_ci.sh # upload build-artifacts to branch-gh-pages - run: sh jslint_ci.sh shCiArtifactUpload - env: - CI_MAIN_JOB: v16.x64.linux - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # shCiArtifactUpload - end diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 8b34809ae..61ea6dc70 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -14,7 +14,7 @@ jobs: # npm publish - start - build: + job1: strategy: matrix: architecture: diff --git a/CHANGELOG.md b/CHANGELOG.md index c05373b6d..b56daceca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ - perf - improve performance by hoisting inlined regexps out of loops and subfunctions # v2022.3.1-beta +- website - use localStorage to persist jslint-options selected in ui +- website - add optional debug-mode to use sessionStorage to persist jslint-globals and jslint-source from ui - jslint - add numeric-separator support - jslint - move regexp-literals to module-level so they are explicitly cached, to improve performance - ci - add check for package.json.fileCount diff --git a/index.html b/index.html index 176354bcf..e6329899c 100644 --- a/index.html +++ b/index.html @@ -1212,22 +1212,22 @@ /*jslint beta, browser*/ +/*global CodeMirror*/ + /*property - ch, - jslint_edition, jslint_report, - setCursor, styleActiveLine, - tagName, - CodeMirror, Pos, Tab, addEventListener, checked, click, closest, column, - ctrlKey, currentTarget, dispatchEvent, display, dom_style_report_unmatched, - editor, error, extraKeys, focus, forEach, from, fromTextArea, - globals, gutters, indentSelection, indentUnit, indentWithTabs, innerHTML, - jslint, key, length, line, lineNumbers, lineWrapping, lint, lintOnChange, - map, matchBrackets, message, metaKey, mode, mode_stop, name, offsetWidth, - onclick, onkeyup, outerHTML, performLint, preventDefault, push, - querySelector, querySelectorAll, registerHelper, replace, replaceSelection, - result, reverse, search, setSize, setValue, severity, - showTrailingSpace, somethingSelected, sort, split, stopPropagation, style, - target, test, textContent, to, trim, value, warnings, width + Pos, Tab, addEventListener, ch, checked, clear, click, closest, column, + ctrlKey, display, dom_style_report_unmatched, editor, error, extraKeys, + focus, forEach, from, fromTextArea, getItem, getValue, globals, gutters, + indentSelection, indentUnit, indentWithTabs, innerHTML, jslint, + jslint_edition, jslint_report, jslint_result, key, length, line, + lineNumbers, lineWrapping, lint, lintOnChange, map, matchBrackets, message, + metaKey, mode, mode_stop, name, offsetWidth, on, outerHTML, parse, + performLint, preventDefault, push, querySelector, querySelectorAll, + registerHelper, replace, replaceSelection, result, reverse, search, + setCursor, setItem, setSize, setValue, severity, showTrailingSpace, + somethingSelected, sort, split, stopPropagation, stringify, style, + styleActiveLine, tagName, target, test, textContent, to, trim, value, + warnings, width */ import jslint from "./jslint.mjs"; @@ -1240,6 +1240,7 @@ lintOnChange: false }; let mode_debug; +let mode_persist_state_debouncing; function dom_style_report_unmatched() { @@ -1273,7 +1274,8 @@ }); } -async function jslint_ui_call() { +async function jslint_run() { + // This function will run jslint in browser and create html-reports. // Show ui-loader-animation. @@ -1295,6 +1297,11 @@ ).forEach(function (elem) { jslint_option_dict[elem.value] = elem.checked; }); + jslint_option_dict.globals = document.querySelector( + "#JSLINT_GLOBALS" + ).value.trim().split( + /[\s,;'"]+/ + ); // Execute linter. @@ -1306,7 +1313,7 @@ document.querySelector( "#JSLINT_REPORT_HTML" ).outerHTML = jslint.jslint_report(jslint_option_dict.result); - jslint_ui_onresize(); + listener_window_onresize(); } catch (err) { console.error(err); //jslint-quiet } @@ -1318,23 +1325,6 @@ }, 500); } -function jslint_ui_onresize() { - let content_width = document.querySelector( - "#JSLINT_OPTIONS" - ).offsetWidth; - -// Set explicit content-width for overflow to work properly. - - document.querySelectorAll( - ".JSLINT_ fieldset > div" - ).forEach(function (elem) { - if (!elem.closest("#JSLINT_OPTIONS")) { - elem.style.width = content_width + "px"; - } - }); - editor.setSize(content_width); -} - function jslint_wrapper_codemirror(CodeMirror) { // This function will integrate jslint with CodeMirror's lint addon. @@ -1342,6 +1332,10 @@ CodeMirror.registerHelper("lint", "javascript", function (text, options) { options.result = jslint.jslint(text, options, options.globals); + +// Debug jslint result. + + window.jslint_result = options.result; return options.result.warnings.map(function ({ column, line, @@ -1362,137 +1356,282 @@ }); } -(function () { - let CodeMirror = window.CodeMirror; - -// Init edition. +function listener_window_onresize() { - document.querySelector("#JSLINT_EDITION").textContent = ( - "Edition: " + jslint.jslint_edition - ); +// This function will properly size ui-elements given current browser +// dimensions. -// Init mode_debug. - mode_debug = ( - /\bdebug=1\b/ - ).test(location.search); + let content_width = document.querySelector( + "#JSLINT_OPTIONS" + ).offsetWidth; -// Init CodeMirror editor. +// Set explicit content-width for overflow to work properly. - editor = CodeMirror.fromTextArea(document.querySelector( - "#JSLINT_SOURCE textarea" - ), { - extraKeys: { - "Shift-Tab": "indentLess", - Tab: function (cm) { - if (cm.somethingSelected()) { - cm.indentSelection("add"); - return; - } - cm.replaceSelection(" "); - } - }, - gutters: ["CodeMirror-lint-markers"], - indentUnit: 4, - indentWithTabs: false, - lineNumbers: true, - lineWrapping: true, - lint: jslint_option_dict, - matchBrackets: true, - mode: "text/javascript", - showTrailingSpace: true, - styleActiveLine: true + document.querySelectorAll( + ".JSLINT_ fieldset > div" + ).forEach(function (elem) { + if (!elem.closest("#JSLINT_OPTIONS")) { + elem.style.width = content_width + "px"; + } }); - window.editor = editor; + editor.setSize(content_width); +} -// Init CodeMirror linter. +function persist_state(mode) { - jslint_wrapper_codemirror(CodeMirror); +// PR-391 - Persist ui-state to localStorage and sessionStorage. -// Init event-handling. + let state; - document.addEventListener("keydown", function (evt) { - switch ((evt.ctrlKey || evt.metaKey) && evt.key) { - case "Enter": - case "e": - evt.preventDefault(); - evt.stopPropagation(); - jslint_ui_call(); - break; +// Debounce this function. + + if (mode !== "mode_debounced") { + if (!mode_persist_state_debouncing) { + mode_persist_state_debouncing = true; + setTimeout(persist_state, 100, "mode_debounced"); } + return; + } + mode_persist_state_debouncing = undefined; + +// Persist jslint-options to localStorage. + + state = {}; + document.querySelectorAll( + "#JSLINT_OPTIONS input[type=checkbox]" + ).forEach(function (elem) { + state[elem.value] = elem.checked; }); - document.querySelector( - ".JSLINT_" - ).onclick = function ({ - target - }) { - switch (target.tagName) { + try { + localStorage.setItem("jslint_persist_options", JSON.stringify(state)); + } catch (ignore) { + localStorage.clear(); + localStorage.setItem("jslint_persist_options", JSON.stringify(state)); + } -// PR-368 - website -// Add clickable-links to editor-code in report-warnings and report-functions. +// Persist jslint-globals to sessionStorage. - case "ADDRESS": - editor.focus(); - editor.setCursor({ - ch: Number(target.textContent.split(":")[1] - 1), - line: Number(target.textContent.split(":")[0] - 1) - }); - break; + if (mode_debug) { + state = document.querySelector("#JSLINT_GLOBALS").value; + if (state.length <= 0x10000) { + try { + sessionStorage.setItem("jslint_persist_globals", state); + } catch (ignore) { + sessionStorage.clear(); + sessionStorage.setItem("jslint_persist_globals", state); + } } - }; - document.querySelector( - "#JSLINT_BUTTONS" - ).onclick = function ({ - target - }) { - switch (target.name) { - case "JSLint": - jslint_ui_call(); - break; - case "clear_options": - document.querySelectorAll( - "#JSLINT_OPTIONS input[type=checkbox]" - ).forEach(function (elem) { - elem.checked = false; - }); - document.querySelector("#JSLINT_GLOBALS").value = ""; - document.querySelector("#JSLINT_OPTIONS").click(); - document.querySelector("#JSLINT_GLOBALS").dispatchEvent( - new Event("keyup") - ); - break; - case "clear_source": - editor.setValue(""); - editor.focus(); - break; + } + +// Persist jslint-source to sessionStorage. + + if (mode_debug) { + state = editor.getValue(); + if (state.length <= 0x10000) { + try { + sessionStorage.setItem("jslint_persist_source", state); + } catch (ignore) { + sessionStorage.clear(); + sessionStorage.setItem("jslint_persist_source", state); + } } - }; - document.querySelector( - "#JSLINT_GLOBALS" - ).onkeyup = function ({ - currentTarget - }) { - jslint_option_dict.globals = currentTarget.value.trim().split( - /[\s,;'"]+/ - ); - }; - document.querySelector( - "#JSLINT_OPTIONS" - ).onclick = function (evt) { - let elem; - elem = evt.target.closest( - "#JSLINT_OPTIONS div[title]" - ); - elem = elem && elem.querySelector("input[type=checkbox]"); - if (elem && elem !== evt.target) { + } +} + +// Init event-handling for hotkeys. + +document.addEventListener("keydown", function (evt) { + switch ((evt.ctrlKey || evt.metaKey) && evt.key) { + case "Enter": + case "e": + evt.preventDefault(); + evt.stopPropagation(); + jslint_run(); + break; + } +}); + +// Init event-handling for #JSLINT_. + +document.querySelector( + ".JSLINT_" +).addEventListener("click", function ({ + target +}) { + switch (target.tagName) { + +// PR-368 - website +// Add clickable-links to editor-code in report-warnings and report-functions. + + case "ADDRESS": + editor.focus(); + editor.setCursor({ + ch: Number(target.textContent.split(":")[1] - 1), + line: Number(target.textContent.split(":")[0] - 1) + }); + break; + } +}); + +// Init event-handling for #JSLINT_BUTTONS. + +document.querySelector( + "#JSLINT_BUTTONS" +).addEventListener("click", function ({ + target +}) { + switch (target.name) { + case "JSLint": + jslint_run(); + break; + case "clear_options": + document.querySelectorAll( + "#JSLINT_OPTIONS input[type=checkbox]" + ).forEach(function (elem) { + elem.checked = false; + }); + document.querySelector("#JSLINT_OPTIONS").click(); + document.querySelector("#JSLINT_GLOBALS").value = ""; + persist_state(); + break; + case "clear_source": + editor.setValue(""); + editor.focus(); + break; + } +}); + +// Init event-handling for #JSLINT_GLOBALS. + +document.querySelector( + "#JSLINT_GLOBALS" +).addEventListener("keyup", persist_state); + +// Init event-handling for #JSLINT_OPTIONS. + +document.querySelector( + "#JSLINT_OPTIONS" +).addEventListener("click", function (evt) { + let elem = evt.target.closest( + "#JSLINT_OPTIONS div[title]" + ); + elem = elem && elem.querySelector("input[type=checkbox]"); + if (elem) { + if (elem !== evt.target) { evt.preventDefault(); evt.stopPropagation(); elem.checked = !elem.checked; } - }; - window.addEventListener("load", jslint_ui_onresize); - window.addEventListener("resize", jslint_ui_onresize); - if (!mode_debug) { - editor.setValue(String(` + persist_state(); + } +}); + +// Init event-handling for window.onload. + +window.addEventListener("load", function () { + let state; + +// Resize ui-elements. + + listener_window_onresize(); + +// PR-391 - Initialize jslint-options from localStorage. + + try { + state = JSON.parse(localStorage.getItem("jslint_persist_options")); + } catch (ignore) {} + state = state || {}; + document.querySelectorAll( + "#JSLINT_OPTIONS input[type=checkbox]" + ).forEach(function (elem) { + if (state[elem.value] === true) { + elem.checked = true; + } + }); + +// PR-391 - Initialize jslint-globals from sessionStorage. + + if (mode_debug) { + try { + document.querySelector( + "#JSLINT_GLOBALS" + ).value = sessionStorage.getItem("jslint_persist_globals"); + } catch (ignore) {} + } + +// PR-391 - Initialize jslint-source from sessionStorage. + + if (mode_debug) { + try { + editor.setValue(sessionStorage.getItem("jslint_persist_source")); + } catch (ignore) {} + } + +// JSLint current source code. + + document.querySelector("button[name='JSLint']").click(); +}); + +// Init event-handling for window.onresize. + +window.addEventListener("resize", listener_window_onresize); + +// Init edition. + +document.querySelector("#JSLINT_EDITION").textContent = ( + "Edition: " + jslint.jslint_edition +); + +// Init mode_debug. + +mode_debug = ( + /\bdebug=1\b/ +).test(location.search); +if (mode_debug) { + document.querySelector( + "#JSLINT_OPTIONS input[value=trace]" + ).click(); +} + +// Init CodeMirror editor. + +editor = CodeMirror.fromTextArea(document.querySelector( + "#JSLINT_SOURCE textarea" +), { + extraKeys: { + "Shift-Tab": "indentLess", + Tab: function (cm) { + if (cm.somethingSelected()) { + cm.indentSelection("add"); + return; + } + cm.replaceSelection(" "); + } + }, + gutters: ["CodeMirror-lint-markers"], + indentUnit: 4, + indentWithTabs: false, + lineNumbers: true, + lineWrapping: true, + lint: jslint_option_dict, + matchBrackets: true, + mode: "text/javascript", + showTrailingSpace: true, + styleActiveLine: true +}); + +// Init CodeMirror linter. + +jslint_wrapper_codemirror(CodeMirror); + +// Init event-handling for CodeMirror editor. + +editor.on("change", persist_state); + +// Init default source code to lint. + +if (!mode_debug) { + editor.setValue(String(` #!/usr/bin/env node /*jslint browser, node*/ /*global caches, indexedDb*/ @@ -1546,18 +1685,11 @@ console.error(formatted_message); }); }()); - `).trim() + "\n"); - } - if (mode_debug) { - document.querySelector( - "#JSLINT_OPTIONS input[value=trace]" - ).click(); - } - document.querySelector("button[name='JSLint']").click(); - -// Debug css-style. + `).trim() + "\n"); +} - window.dom_style_report_unmatched = dom_style_report_unmatched; -}()); +// Init exports. +window.dom_style_report_unmatched = dom_style_report_unmatched; +window.editor = editor; diff --git a/jslint.mjs b/jslint.mjs index 0701ee422..8cd2a35bb 100644 --- a/jslint.mjs +++ b/jslint.mjs @@ -9443,6 +9443,9 @@ body { : "global" ); if (global.length + froms.length + exports.length > 0) { + if (functions.length === 0) { + html += "
\n"; + } html += "
\n"; html += detail(module, global); html += detail("import from", froms); diff --git a/jslint_ci.sh b/jslint_ci.sh index 95fac3c61..60355fc59 100644 --- a/jslint_ci.sh +++ b/jslint_ci.sh @@ -8,6 +8,7 @@ # git add .; npm run test2; git checkout . # git branch -d -r origin/aa # git config --global diff.algorithm histogram +# git fetch --prune # git fetch origin alpha beta master && git fetch upstream alpha beta master # git fetch origin alpha beta master --tags # git fetch upstream "refs/tags/*:refs/tags/*" @@ -242,12 +243,12 @@ import moduleUrl from "url"; shCiArtifactUpload() {(set -e # this function will upload build-artifacts to branch-gh-pages - export GITHUB_BRANCH0="$(git rev-parse --abbrev-ref HEAD)" - local FILE if (! shCiIsMainJob) then return fi + export GITHUB_BRANCH0="$(git rev-parse --abbrev-ref HEAD)" + local FILE # init .git/config git config --local user.email "github-actions@users.noreply.github.com" git config --local user.name "github-actions" @@ -308,14 +309,10 @@ import moduleChildProcess from "child_process"; git checkout -b gh-pages origin/gh-pages # update dir branch-$GITHUB_BRANCH0 rm -rf "branch-$GITHUB_BRANCH0" - mkdir -p "branch-$GITHUB_BRANCH0" - (set -e - cd "branch-$GITHUB_BRANCH0" - git init -b branch1 - git pull --depth=1 .. "$GITHUB_BRANCH0" - rm -rf .git - git add -f . - ) + git clone . "branch-$GITHUB_BRANCH0" --branch="$GITHUB_BRANCH0" + rm -rf "branch-$GITHUB_BRANCH0/.git" + # add dir branch-$GITHUB_BRANCH0 + git add -f "branch-$GITHUB_BRANCH0" # update root-dir with branch-beta if [ "$GITHUB_BRANCH0" = beta ] then @@ -346,7 +343,7 @@ import moduleChildProcess from "child_process"; if [ "$(git rev-list --count gh-pages)" -gt 50 ] then # backup - shGitCmdWithGithubToken push origin -f gh-pages:gh-pages-backup + git push origin -f gh-pages:gh-pages-backup # squash commits git checkout --orphan squash1 git commit --quiet -am squash || true @@ -354,12 +351,12 @@ import moduleChildProcess from "child_process"; git push . -f squash1:gh-pages git checkout gh-pages # force-push squashed-commit - shGitCmdWithGithubToken push origin -f gh-pages + git push origin -f gh-pages fi # list files shGitLsTree # push branch-gh-pages - shGitCmdWithGithubToken push origin gh-pages + git push origin gh-pages # validate http-links (set -e cd "branch-$GITHUB_BRANCH0" @@ -513,7 +510,7 @@ shCiIsMainJob() {(set -e # this function will return 0 if current ci-job is main job node --input-type=module --eval ' process.exit(Number( - `${process.version.split(".")[0]}.${process.arch}.${process.platform}` + `node.${process.version.split(".")[0]}.${process.arch}.${process.platform}` !== process.env.CI_MAIN_JOB )); ' "$@" # ' @@ -538,6 +535,11 @@ shCiNpmPublishCustom() {(set -e # npm publish --access public )} +shCiPre() {(set -e +# this function will run pre-ci + return +)} + shDiffFileFromDir() {(set -e # this function print diff of file $1 against same file in dir $2 diff -u "$1" "$2/$1" @@ -647,22 +649,28 @@ shDuList() {(set -e )} shGitCmdWithGithubToken() {(set -e -# this function will run git $CMD with $GITHUB_TOKEN +# this function will run git $CMD with $MY_GITHUB_TOKEN local CMD local EXIT_CODE - local REMOTE local URL printf "shGitCmdWithGithubToken $*\n" CMD="$1" shift - REMOTE="$1" + URL="$1" shift - URL="$( - git config "remote.$REMOTE.url" \ - | sed -e "s|https://|https://x-access-token:$GITHUB_TOKEN@|" - )" + if (printf "$URL" | grep -qv "^https://") + then + URL="$(git config "remote.$URL.url")" + fi + if [ "$MY_GITHUB_TOKEN" ] + then + URL="$( + printf "$URL" \ + | sed -e "s|https://|https://x-access-token:$MY_GITHUB_TOKEN@|" + )" + fi EXIT_CODE=0 - # hide $GITHUB_TOKEN in case of err + # hide $MY_GITHUB_TOKEN in case of err git "$CMD" "$URL" "$@" 2>/dev/null || EXIT_CODE="$?" printf "shGitCmdWithGithubToken - EXIT_CODE=$EXIT_CODE\n" 1>&2 return "$EXIT_CODE" @@ -826,7 +834,7 @@ function assertOrThrow(condition, message) { moduleHttps.request(`${url}?ref=${branch}`, { headers: { accept: "application/vnd.github.v3+json", - authorization: `token ${process.env.GITHUB_TOKEN}`, + authorization: `token ${process.env.MY_GITHUB_TOKEN}`, "user-agent": "undefined" }, method