diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000000..a3d789ae81 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,15 @@ +.DS_Store +build +docs/_build +__pycache__ +*.swp +node_modules +.project + +# editors +.idea/ +.vscode/ +.Rproj.user + +# misc +/work diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000000..61ad70d2e8 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,120 @@ +module.exports = { + env: { + browser: true, + es6: true, + node: true + }, + extends: [ + "eslint:recommended", + "standard" + ], + parserOptions: { + ecmaVersion: 2015, + sourceType: "module" + }, + plugins: [ + "@typescript-eslint" + ], + rules: { + "no-var": "warn", + "init-declarations": ["error", "always"], + "array-callback-return": "error", + "block-scoped-var": "error", + "no-multiple-empty-lines": ["error", { max: 2 }], + // we like our semi-colons + semi: ["error", "always"], + // our codebase doesn't do this at all, so disabled for now + "space-before-function-paren": ["error", "never"], + // for now ignore diff between types of quoting + quotes: "off", + // this is the style we are already using + "operator-linebreak": ["error", "before", { + overrides: { + "=": "after" + } + }], + // sometimes we declare variables with extra spacing + indent: ["error", 2, { VariableDeclarator: 2 }], + // seems like a good idea not to use explicit undefined + "no-undefined": "error", + // ensure import specifier contains file extension + "import/extensions": ["error", "always"] + }, + overrides: [ + { + files: ["types/*.ts", "src/*.ts"], + parser: '@typescript-eslint/parser', + rules: { + "import/no-duplicates": "off", + "import/extensions": "off" + } + }, + { + files: ["src/**/*.js"], + rules: { + // make sure there is no Node.js specific API slipping into the source files + "import/no-nodejs-modules": "error", + "import/no-commonjs": "error" + } + }, + { + files: ["src/languages/*.js"], + rules: { + "no-unused-expressions": "off", + // languages are all over the map and we don't want to + // do a mass edit so turn off the most egregious rule violations + // indent: "off", + camelcase: "off", + "no-control-regex": "off", + "no-useless-escape": "off", + "comma-dangle": "off", + "array-bracket-spacing": ["error", "always" + // { + // objectsInArrays: true + // } + ], + // "object-curly-spacing": "warn", + // "key-spacing": "off", + // "array-bracket-spacing": ["warn"], + "array-bracket-newline": ["warn", { + multiline: true, + minItems: 2 + }], + "array-element-newline": "warn", + "object-curly-newline": [1, { + minProperties: 2 + }], + "object-property-newline": [2, + { allowAllPropertiesOnSameLine: false } + ] + } + }, + { + files: ["demo/**/*.js"], + globals: { + hljs: "readonly" + } + }, + { + files: ["test/**/*.js"], + globals: { + should: "readonly" + }, + env: { + mocha: true + }, + parserOptions: { + ecmaVersion: 2018 + } + }, + { + files: ["tools/**/*.js"], + parserOptions: { + ecmaVersion: 2018 + }, + rules: { + camelcase: "off" + } + } + ] +}; diff --git a/.eslintrc.lang.js b/.eslintrc.lang.js new file mode 100644 index 0000000000..bae3e669a0 --- /dev/null +++ b/.eslintrc.lang.js @@ -0,0 +1,14 @@ +module.exports = { + env: { + browser: true, + es6: true, + node: true + }, + parserOptions: { + ecmaVersion: 2015, + sourceType: "module" + }, + // no rules, this file exists only to lint the grammars and check + // that no ES2018 or newer syntax has crept in by accident + rules: {} +}; diff --git a/.github/ISSUE_TEMPLATE/1_incorrect-syntax-highlighting.md b/.github/ISSUE_TEMPLATE/1_incorrect-syntax-highlighting.md new file mode 100644 index 0000000000..2da7594b42 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/1_incorrect-syntax-highlighting.md @@ -0,0 +1,39 @@ +--- +name: Report code that we highlight wrong +about: You have a specific snippet of code that is not highlighted correctly. +title: "(language name) Short description of issue..." +labels: bug, help welcome, language +assignees: '' + +--- + +**Describe the issue** + + +**Which language seems to have the issue?** + + +**Are you using `highlight` or `highlightAuto`?** + +... + +**Sample Code to Reproduce** + + + +**Expected behavior** + + +**Additional context** + diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000000..f14a0a98df --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,31 @@ +--- +name: Report some other bug +about: You are seeing other incorrect, unexpected or unexplained behavior. +title: "" +labels: bug, help welcome, parser +assignees: '' + +--- + +**Describe the issue/behavior that seems buggy** + + + +**Sample Code or Instructions to Reproduce** + + + +**Expected behavior** + + +**Additional context** + diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000..d470786017 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,11 @@ +blank_issues_enabled: false +contact_links: + - name: Join our friendly Discord community + url: https://discord.gg/M24EbU7ja9 + about: If you'd like to hang out, help out, or need some help - we're here. + - name: Read our documentation + url: https://highlightjs.readthedocs.io/ + about: Sometimes the answers are right there, just waiting to be discovered. + - name: Request a new language + url: https://highlightjs.readthedocs.io/en/latest/language-requests.html + about: Please read our policy on requesting new languages. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 9bb5ffd29d..84913bdcbd 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,8 +1,8 @@ --- -name: Feature request -about: Suggest a new idea or feature... -title: "[Request] ..." -labels: enhancement +name: Request a new feature (other than a new language) +about: You'd like to suggest a potentially helpful new feature... for requesting languages look below. +title: "[Request] Short description of the new feature..." +labels: enhancement, parser assignees: '' --- diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000000..550bc88382 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,11 @@ + + + + + +### Changes + + +### Checklist +- [ ] Added markup tests, or they don't apply here because... +- [ ] Updated the changelog at `CHANGES.md` diff --git a/.github/workflows/greetings.yml b/.github/workflows/greetings.yml deleted file mode 100644 index e390b60c28..0000000000 --- a/.github/workflows/greetings.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: Greetings - -on: [pull_request, issues] - -jobs: - greeting: - runs-on: ubuntu-latest - steps: - - uses: actions/first-interaction@v1 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} -# issue-message: 'Message that will be displayed on users'' first issue' - pr-message: > - Thank you so much for your first contribution! It's appreciated. - One of our maintainers will review it as soon as they can. - - If you didn't modify `CHANGES.md` already, please go and do that now and push a second - commit. The `CHANGES.md` file is where we keep a running summary of changes so users - know what to expect when we issue periodic releases. - - Again, thanks. - - --- - - If by some chance you're just chomping at the bit to make a second PR we do have a "beginner friendly" tag: - https://github.com/highlightjs/highlight.js/labels/beginner%20friendly diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000000..e8e8ac936e --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,21 @@ +# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions + +name: Lint + +on: + pull_request: + branches: + - main + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: "*" # latest + - run: npm ci + - run: npm run lint + - run: npm run lint-languages diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml new file mode 100644 index 0000000000..55c5aa64d6 --- /dev/null +++ b/.github/workflows/node.js.yml @@ -0,0 +1,53 @@ +# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions + +name: Node.js CI + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + build: + + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [12.x, 14.x, 16.x] + build-how: ["node", "browser", "browser -n", "cdn :common"] + + steps: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - run: npm install + - run: node ./tools/build.js -t ${{ matrix.build-how }} + # normal browser build + - if: contains(matrix.build-how, 'browser') || contains(matrix.build-how, 'cdn') + name: Test browser/CDN build + run: | + npm run test-browser + node test/builds/browser_build_as_commonjs.js + # CDN build should be easily importable + - if: contains(matrix.build-how, 'cdn') + name: Test that we can import CDN esm build + run: | + node test/builds/cdn_build_as_esm.mjs + + - if: contains(matrix.build-how, 'node') + name: Test Node.js build + run: | + npm test + + # test that our build is "use strict" safe for use with packaging + # systems importing our source thru ES6 modules (rollup, etc.) + - if: contains(matrix.build-how, 'node') + name: Test Node.js build is "use strict" safe + run: | + ./node_modules/.bin/rollup -c test/builds/rollup_import_via_commonjs.js + node build/bundle.js diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000000..0fe91ac9e5 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,134 @@ +name: Release + +on: + push: + tags: + - "*alpha*" + - "*pre*" + - "*beta*" + - "1[0-9]+.[0-9]+.[0-9]+" + +jobs: + prerelease: + name: Pre-release + runs-on: ubuntu-latest + steps: + - name: Checkout highlight.js + uses: actions/checkout@v2 + + - name: Tag is ${{ github.ref }}. + # we have to repeat ourselves here since the environment is not actually updated + # until the next step executes, so we can't access $TAG_NAME yet + run: | + echo "TAG_NAME=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV + echo "MAJOR_VERSION=$(echo ${GITHUB_REF/refs\/tags\//} | cut -d'.' -f1)" >> $GITHUB_ENV + - name: Make sure we are pushing a tag... + if: ${{ !contains(github.ref,'refs/tags/') }} + run: false + # run: echo "TAG_NAME=0.0.0-test0" >> $GITHUB_ENV + + - if: contains(github.ref, 'beta') || contains(github.ref, 'pre') || contains(github.ref, 'alpha') + run: | + echo "NPM_TAG=beta" >> $GITHUB_ENV + echo "RELEASING=beta" >> $GITHUB_ENV + + - if: ${{ !(contains(github.ref, 'beta') || contains(github.ref, 'pre') || contains(github.ref, 'alpha')) }} + run: | + echo "NPM_TAG=latest" >> $GITHUB_ENV + echo "RELEASING=stable" >> $GITHUB_ENV + + - name: match-tag-to-package-version + uses: geritol/match-tag-to-package-version@0.0.2 + env: + TAG_PREFIX: refs/tags/ # Optional, default prefix refs/tags/ + + - name: Use Node.js 15.x + uses: actions/setup-node@v1 + with: + node-version: 15.x + - name: Build Node.js package + run: | + npm install + node ./tools/build.js -t node + npm test + + - name: Publish highlight.js to NPM + id: publish + uses: JS-DevTools/npm-publish@v1 + with: + check-version: true + token: ${{ secrets.NPM_TOKEN }} + package: ./build/package.json + tag: ${{ env.NPM_TAG }} + + - if: steps.publish.outputs.type != 'none' + run: | + echo "Version changed: ${{ steps.publish.outputs.old-version }} => ${{ steps.publish.outputs.version }}" + + # if stable release + - name: Stable Release + if: env.RELEASING == 'stable' + run: echo "BRANCH_NAME=${MAJOR_VERSION}-stable" >> $GITHUB_ENV + # else (beta) + - name: Beta Release + if: env.RELEASING == 'beta' + run: echo "BRANCH_NAME=main" >> $GITHUB_ENV + - name: Confirm release is either stable or beta + if: ${{ !(env.RELEASING == 'stable' || env.RELEASING == 'beta') }} + run: | + echo We seem to be releasing `${RELEASING}`. + false + + - name: Checkout cdn-release + uses: actions/checkout@v2 + with: + repository: 'highlightjs/cdn-release' + path: 'cdn-release' + token: ${{ secrets.CDN_REPO_TOKEN }} + ref: ${{ env.BRANCH_NAME }} + + - name: Build CDN package + run: node ./tools/build.js -t cdn :common + + - name: Commmit & Push cdn-release ${{ env.TAG_NAME }} + working-directory: ./cdn-release + run: | + rm -r ./build + mv ../build/ ./build/ + mv ./build/DIGESTS.md . + git config user.name github-actions + git config user.email github-actions@github.com + git add ./build/ + git add ./DIGESTS.md + git commit -m'Update to version ${{ env.TAG_NAME }}' + git tag ${TAG_NAME} + git push -f --atomic origin ${BRANCH_NAME} ${TAG_NAME} + + - name: Publish cdn-assets to NPM + id: publish_cdn + uses: JS-DevTools/npm-publish@v1 + with: + check-version: true + token: ${{ secrets.NPM_TOKEN }} + package: ./cdn-release/build/package.json + tag: ${{ env.NPM_TAG }} + + # log.info('Updating CDN repo at %s' % settings.HLJS_CDN_SOURCE) + # run(['nodejs', 'tools/build.js', '--target', 'cdn', ':common']) + # os.chdir(settings.HLJS_CDN_SOURCE) + # run(['git', 'pull', '-f']) + # lines = run(['git', '--git-dir', os.path.join(settings.HLJS_CDN_SOURCE, '.git'), 'tag']) + # build_dir = os.path.join(settings.HLJS_CDN_SOURCE, 'build') + # if version in lines: + # log.info('Tag %s already exists in the local CDN repo' % version) + # else: + # if os.path.exists(build_dir): + # shutil.rmtree(build_dir) + # shutil.move(os.path.join(settings.HLJS_SOURCE, 'build'), build_dir) + # run(['git', 'add', '.']) + # run(['git', 'commit', '-m', 'Update to version %s' % version]) + # run(['git', 'tag', version]) + # run(['git', 'push']) + # run(['git', 'push', '--tags']) + # npm_publish(build_dir) + # os.chdir(settings.HLJS_SOURCE) diff --git a/.github/workflows/size_report.yml b/.github/workflows/size_report.yml new file mode 100644 index 0000000000..0d9a047ef3 --- /dev/null +++ b/.github/workflows/size_report.yml @@ -0,0 +1,22 @@ +# This workflow computes the size of a CDN build's output on all Javascript files. +# Reported file sizes are after the result of gzip compression. +# Compression action used: https://github.com/preactjs/compressed-size-action + +name: Size Report (gzip) + +on: [pull_request] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Compute Compressed Size + uses: preactjs/compressed-size-action@v2 + with: + build-script: "build-cdn" + compression: "gzip" + minimum-change-threshold: 5 # bytes + pattern: "./build/{*.min.js,es/*.min.js,languages/*.min.js,es/languages/*.min.js}" diff --git a/.gitignore b/.gitignore index 01c7f25df1..af1812c6b7 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,17 @@ node_modules yarn.lock extra/ +# play stuff +quick* +test*.* +extra* + # editors .idea/ +.nova/ .vscode/ .Rproj.user + +# misc +/work +/website diff --git a/.jshintrc b/.jshintrc deleted file mode 100644 index 05e63ed4e0..0000000000 --- a/.jshintrc +++ /dev/null @@ -1,5 +0,0 @@ -// whole codebase isn't ES8/9 yet, but our tests and some things are -{ - "esversion": 9, - "node": true -} diff --git a/.mocharc.json b/.mocharc.json new file mode 100644 index 0000000000..ef2f5d36c8 --- /dev/null +++ b/.mocharc.json @@ -0,0 +1,3 @@ +{ + "require": "should" +} diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000000..0aeaa7cdb5 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,20 @@ +# .readthedocs.yaml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Build documentation in the docs/ directory with Sphinx +sphinx: + configuration: docs/conf.py + +# Optionally build your docs in additional formats such as PDF +# formats: + # - pdf + +# Optionally set the version of Python and requirements required to build your docs +python: + version: 3.7 + install: + - requirements: docs/requirements.txt diff --git a/.tokeignore b/.tokeignore new file mode 100644 index 0000000000..783c23bc18 --- /dev/null +++ b/.tokeignore @@ -0,0 +1,7 @@ +test/* +src/styles/* +*.md +*.json +tools +docs +types diff --git a/.travis.yml b/.travis.yml index fa61a6a0e9..01429794df 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,6 +28,8 @@ script: if [ "x$BUILD" = "xnode" ]; then npm run test else - npm run test-browser + npm run test-browser || exit 1 + # our browser build should also work fine as a Node.js CommonJS module + node test/builds/browser_build_as_commonjs.js fi sudo: false # Use container-based architecture diff --git a/AUTHORS.txt b/AUTHORS.txt deleted file mode 100644 index e86ed42e79..0000000000 --- a/AUTHORS.txt +++ /dev/null @@ -1,287 +0,0 @@ -Syntax highlighting with language autodetection. - -URL: https://highlightjs.org/ - -Current core developers (alphabetical): - -- Gidi Meir Morris -- Jan T. Sott -- Li Xuanji -- Marcos Cáceres -- Sang Dang -- Josh Goebel - -Former maintainers: - -- Ivan Sagalaev (original author) -- Jeremy Hull -- Oleg Efimov - -Contributors: - -- Peter Leonov -- Vanessa Sochat <@vsoch> -- Victor Karamzin -- Vsevolod Solovyov -- Anton Kovalyov -- Nikita Ledyaev -- Konstantin Evdokimenko -- Dmitri Roudakov -- Yuri Ivanov -- Vladimir Ermakov -- Vladimir Gubarkov -- Brian Beck -- MajestiC -- Vasily Polovnyov -- Vladimir Epifanov -- Alexander Makarov -- Vah -- Shuen-Huei Guan -- Jason Diamond -- Michal Gabrukiewicz -- Ruslan Keba -- Sergey Baranov -- Zaripov Yura -- Oleg Volchkov -- Vasily Mikhailitchenko -- Jan Berkel -- Vladimir Moskva -- Loren Segal -- Andrew Fedorov -- Igor Kalnitsky -- Valerii Hiora -- Nikolay Zakharov -- Dmitry Kovega -- Sergey Ignatov -- Antono Vasiljev -- Stephan Kountso -- pumbur -- John Crepezzi -- Andrey Vlasovskikh -- Alexander Myadzel -- Evgeny Stepanischev -- Dmytrii Nagirniak -- Luigi Maselli -- Denis Bardadym -- Aahan Krish -- Ilya Baryshev -- Aleksandar Ruzicic -- Joe Cheng -- Angel G. Olloqui -- Jason Tate -- Sergey Tikhomirov -- Marc Fornos -- Yoshihide Jimbo -- Casey Duncan -- Eugene Nizhibitsky -- Alberto Gimeno -- Kirk Kimmel -- Nathan Grigg -- Dr. Drang -- Robin Ward -- Dmitry Medvinsky -- Jason Jacobson -- Jonas Follesø -- Dan Allen -- noformnocontent -- Damien White -- Alexander Marenin -- Cédric Néhémie -- Simon Madine -- Benjamin Pannell -- Eric Knibbe -- Poren Chiang -- Kelley van Evert -- Kurt Emch -- Mehdi Dogguy -- Nicolas Braud-Santoni -- Ralf Bitter -- Sylvestre Ledru -- Troy Kershaw -- Zena Treep -- Daniel Kvasnicka -- Carlo Kok -- Bram de Haan -- Seongwon Lee -- Zaven Muradyan -- Brent Bradbury -- Martin Dilling-Hansen -- Ilya Vassilevsky -- Josh Adams -- Dan Tao -- Jeff Escalante -- Jun Yang -- Nikolay Lisienko -- Heiko August -- Domen Kožar -- Travis Odom -- innocenat -- Arthur Bikmullin -- Pascal Hurni -- Roman Shmatov -- Nic West -- Panu Horsmalahti -- Flaviu Tamas -- Damian Mee -- Christopher Kaster -- Chris Eidhof -- Nate Cook -- Matt Diephouse -- Erik Osheim -- Guillaume Laforge -- Lucas Mazza -- Maxim Dikun -- Henrik Feldt -- Anton Kochkov -- Michael Allen -- JP Verkamp -- Adam Joseph Cook -- Sergey Vidyuk -- Radek Liska -- Jose Molina Colmenero -- Max Mikhailov -- Bryant Williams -- Erik Paluka -- Luke Holder -- David Mohundro -- Nicholas Blumhardt -- Christophe de Dinechin -- Taneli Vatanen -- Jen Evers-Corvina -- Kassio Borges -- Cedric Sohrauer -- Mickaël Delahaye -- Hakan Özler -- Trey Shugart -- Vincent Zurczak -- Adam Joseph Cook -- Edwin Dalorzo -- mucaho -- Dennis Titze -- Jon Evans -- Brian Quistorff -- Jonathan Suever -- Alexis Hénaut -- Chris Kiehl -- Peter Piwowarski -- Kenta Sato -- Anthony Scemama -- Taufik Nurrohman -- Pedro Oliveira -- Gu Yiling -- Thomas Applencourt -- Andrew Farmer -- Sergey Mashkov -- Raivo Laanemets -- Kenneth Fuglsang -- David Anson -- Louis Barranqueiro -- Tim Schumacher -- Lucas Werkmeister -- Dan Panzarella -- Bruno Dias -- Jay Strybis -- Guillaume Gomez -- Janis Voigtländer -- Dirk Kirsten -- MY Sun -- Vadimtro -- Benjamin Auder -- Dotan Dimet -- Manh Tuan -- Philippe Charrière -- Stefan Bechert -- Samuel Reed -- Yury Selivanov -- Tsuyusato Kitsune -- Mick MacCallum -- Kristoffer Gronlund -- Søren Enevoldsen -- Daniel Rosenwasser -- Ladislav Prskavec -- Jan Kühle -- Stefan Wienert -- Nikita Savchenko -- Stefania Mellai -- Nebuleon Fumika -- prince -- Brendan Rocks -- Raphaël Assénat -- Matt Evans -- Martin Braun -- Boris Cherny -- John Foster -- Robert Dodier -- Anthony Dugois -- Qeole -- Denis Ciccale -- Michael Johnston -- Taras -- Philipp Wolfer -- Mikko Kouhia -- Billy Quith -- Herbert Shin -- Tristano Ajmone -- Taisuke Fujimoto -- Boone Severson -- Victor Zhou -- Lars Schulna -- Jacob Childress -- Gavin Siu -- Builder's Brewery -- Sergey Bronnikov -- Joe Eli McIlvain -- Stephan Boyer -- Alex McKibben -- Daniel Gamage -- Matthew Daly -- Magnus Madsen -- Camil Staps -- Alexander Lichter -- Nicolas Le Gall -- Kenton Hamaluik -- Marvin Saignat -- Michael Rodler -- Sergey Sobko -- Hale Chan -- Kasper Andersen -- Philipp A. -- Guannan Wei -- Sam Wu -- Ike Ku -- Andres Täht -- Rene Saarsoo -- Jordi Petit -- Raphaël Parrëe -- Joël Porquet -- Alex Arslan -- Stanislav Belov -- Ivan Dementev -- Nicolas LLOBERA -- Morten Piibeleht -- Martin Clausen -- Arctic Ice Studio -- Google Inc. (David Benjamin) -- Ahmad Awais -- Duncan Paterson -- Tristian Kelly -- Melissa Geels -- Dmitriy Tarasov -- Egor Rogov -- Meseta -- Harmon -- Eric Bailey -- Gustavo Costa -- Jeffrey Arnold -- Antoine Boisier-Michaud -- Alejandro Isaza -- Laurent Voullemier -- Sean T. Allen -- Greg Cline -- Sejin Jeon -- Taif Alimov -- Yuri Mazursky -- Carl Baxter -- Thomas Reichel -- G8t Guy -- Samia Ali diff --git a/CHANGES.md b/CHANGES.md index f2b85d5c3e..8053906b60 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,20 +1,991 @@ -## Version (master) +## Version 11.5.0 (in progress) + +Themes: +- Added `Tokyo-Night-dark` theme [Henri Vandersleyen][] + +Grammars: + +- fix(markdown) Handle `***Hello world***` without breaking [Josh Goebel][] +- enh(php) add support for PHP Attributes [Wojciech Kania][] +- fix(java) prevent false positive variable init on `else` [Josh Goebel][] +- enh(php) named arguments [Wojciech Kania][] +- fix(php) PHP constants [Wojciech Kania][] +- fix(angelscript) incomplete int8, int16, int32, int64 highlighting [Melissa Geels][] +- enh(ts) modify TypeScript-specific keywords and types list [anydonym][] +- fix(brainfuck) fix highlighting of initial ++/-- [Christina Hanson][] +- fix(llvm) escaping in strings and number formats [Flakebi][] + +[Wojciech Kania]: https://github.com/wkania +[Melissa Geels]: https://github.com/codecat +[anydonym]: https://github.com/anydonym +[henri Vandersleyen]: https://github.com/Vanderscycle +[Christina Hanson]: https://github.com/LyricLy +[Flakebi]: https://github.com/Flakebi +[Josh Goebel]: https://github.com/joshgoebel + + +## Version 11.4.0 + +New Language: + +- Added 3rd party Pine Script grammar to SUPPORTED_LANGUAGES [Jeylani B][] +- Added 3rd party cURL grammar to SUPPORTED_LANGUAGES [highlightjs-curl](https://github.com/highlightjs/highlightjs-curl) + +Themes: + +- `Default` is now much closer WCAG AA (contrast) (#3402) [Josh Goebel] +- `Dark` now meets WCAG AA (contrast) (#3402) [Josh Goebel] +- Added `intellij-light` theme [Pegasis] +- Added `felipec` theme [Felipe Contreras] + +These changes should be for the better and should not be super noticeable but if you're super picky about your colors you may want to intervene here or copy over the older themes from 11.3 or prior. + +Grammars: + +- enh(arcade) updated to ArcGIS Arcade version 1.16 [John Foster][] +- enh(php) Left and right-side of double colon [Wojciech Kania][] +- enh(php) add PHP constants [Wojciech Kania][] +- enh(php) add PHP 8.1 keywords [Wojciech Kania][] +- fix(cpp) fix `vector<<` template false positive (#3437) [Josh Goebel][] +- enh(php) support First-class Callable Syntax (#3427) [Wojciech Kania][] +- enh(php) support class constructor call (#3427) [Wojciech Kania][] +- enh(php) support function invoke (#3427) [Wojciech Kania][] +- enh(php) Switch highlighter to partially case-insensitive (#3427) [Wojciech Kania][] +- enh(php) improve `namespace` and `use` highlighting (#3427) [Josh Goebel][] +- enh(php) `$this` is a `variable.language` now (#3427) [Josh Goebel][] +- enh(php) add `__COMPILER_HALT_OFFSET__` (#3427) [Josh Goebel][] +- enh(js/ts) fix => async function title highlights (#3405) [Josh Goebel][] +- enh(twig) update keywords list (#3415) [Matthieu Lempereur][] +- fix(python) def, class keywords detected mid-identifier (#3381) [Josh Goebel][] +- fix(python) Fix recognition of numeric literals followed by keywords without whitespace (#2985) [Richard Gibson][] +- enh(swift) add SE-0290 unavailability condition (#3382) [Bradley Mackey][] +- fix(fsharp) Highlight operators, match type names only in type annotations, support quoted identifiers, and other smaller fixes. [Melvyn Laïly][] +- enh(java) add `sealed` and `non-sealed` keywords (#3386) [Bradley Mackey][] +- enh(js/ts) improve `CLASS_REFERENCE` (#3411) [Josh Goebel][] +- enh(nsis) Update defines pattern to allow `!` (#3417) [idleberg][] +- enh(nsis) Update language strings pattern to allow `!` (#3420) [idleberg][] +- fix(stan) Updated for Stan 2.28 and other misc. improvements (#3410) +- enh(nsis) Update variables pattern (#3416) [idleberg][] +- fix(clojure) Several issues with Clojure highlighting (#3397) [Björn Ebbinghaus][] + - fix(clojure) `comment` macro catches more than it should (#3395) + - fix(clojure) `$` in symbol breaks highlighting + - fix(clojure) Add complete regex for number detection + - enh(clojure) Add character mode for character literals + - fix(clojure) Inconsistent namespaced map highlighting + - enh(clojure) Add `regex` mode to regex literal + - fix(clojure) Remove inconsistent/broken highlighting for metadata + - enh(clojure) Add `punctuation` mode for commas. +- fix(julia) Enable the `jldoctest` alias (#3432) [Fons van der Plas][] + +Developer Tools: + +- (chore) add gzip size compression report (#3400) [Bradley Mackey][] + +Themes: + +- Modified background color in css for Gradient Light and Gradient Dark themes [Samia Ali][] + +[John Foster]: https://github.com/jf990 +[Pegasis]: https://github.com/PegasisForever +[Wojciech Kania]: https://github.com/wkania +[Jeylani B]: https://github.com/jeyllani +[Richard Gibson]: https://github.com/gibson042 +[Bradley Mackey]: https://github.com/bradleymackey +[Melvyn Laïly]: https://github.com/mlaily +[Björn Ebbinghaus]: https://github.com/MrEbbinghaus +[Josh Goebel]: https://github.com/joshgoebel +[Samia Ali]: https://github.com/samiaab1990 +[Matthieu Lempereur]: https://github.com/MrYamous +[idleberg]: https://github.com/idleberg +[Fons van der Plas]: https://github.com/fonsp +[Felipe Contreras]: https://github.com/felipec + +## Version 11.3.1 + +Build: + +- (fix) Grammar CDN modules not generated correctly. (#3363) [Josh Goebel][] + +[Josh Goebel]: https://github.com/joshgoebel + + +## Version 11.3.0 + +Build: + +- add `HighlightJS` named export (#3295) [Josh Goebel][] +- add `.default` named export to CJS builds (#3333) [Josh Goebel][] + +Parser: + +- add first rough performance testing script (#3280) [Austin Schick][] +- add `throwUnescapedHTML` to warn against potential HTML injection [Josh Goebel][] +- expose `regex` helper functions via `hljs` injection [Josh Goebel][] + - concat + - lookahead + - either + - optional + - anyNumberOfTimes + +Grammars: + +- fix(ts) some complex types would classify as JSX (#3278) [Josh Goebel][] +- fix(js/ts) less false positives for `class X extends Y` (#3278) [Josh Goebel][] +- enh(css): add properties from several W3C (Candidate) Recommendations (#3308) +- fix(js/ts) `Float32Array` highlighted incorrectly (#3353) [Josh Goebel][] +- fix(css) single-colon psuedo-elements no longer break highlighting (#3240) [Josh Goebel][] +- fix(scss) single-colon psuedo-elements no longer break highlighting (#3240) [Josh Goebel][] +- enh(fsharp) rewrite most of the grammar, with many improvements [Melvyn Laïly][] +- enh(go) better type highlighting, add `error` type [Josh Goebel][] +- fix(js/ts) regex inside `SUBST` is no longer highlighted [Josh Goebel][] +- fix(python) added support for unicode identifiers (#3280) [Austin Schick][] +- enh(css/less/stylus/scss) improve consistency of function dispatch (#3301) [Josh Goebel][] +- enh(css/less/stylus/scss) detect block comments more fully (#3301) [Josh Goebel][] +- fix(cpp) switch is a keyword (#3312) [Josh Goebel][] +- fix(cpp) fix `xor_eq` keyword highlighting. [Denis Kovalchuk][] +- enh(c,cpp) highlight type modifiers as type (#3316) [Josh Goebel][] +- enh(css/less/stylus/scss) add support for CSS Grid properties [monochromer][] +- enh(java) add support for Java Text Block (#3322) [Teletha][] +- enh(scala) add missing `do` and `then` keyword (#3323) [Nicolas Stucki][] +- enh(scala) add missing `enum`, `export` and `given` keywords (#3328) [Nicolas Stucki][] +- enh(scala) remove symbol syntax and fix quoted code syntax (#3324) [Nicolas Stucki][] +- enh(scala) add Scala 3 `extension` soft keyword (#3326) [Nicolas Stucki][] +- enh(scala) add Scala 3 `end` soft keyword (#3327) [Nicolas Stucki][] +- enh(scala) add `inline` soft keyword (#3329) [Nicolas Stucki][] +- enh(scala) add `using` soft keyword (#3330) [Nicolas Stucki][] +- enh(fsharp) added `f#` alias (#3337) [Bahnschrift][] +- enh(bash) added gnu core utilities (#3342) [katzeprior][] +- enh(nsis) add new NSIS commands (#3351) [idleberg][] +- fix(nsis) set `case_insensitive` to `true` (#3351) [idleberg][] +- fix(css/less/stylus/scss) highlight single-colon psuedo-elements properly (#3240) [zsoltlengyelit][] +- fix(css) add css hex color alpha support (#3360) [ierehon1905][] + +[Austin Schick]: https://github.com/austin-schick +[Josh Goebel]: https://github.com/joshgoebel +[Denis Kovalchuk]: https://github.com/deniskovalchuk +[monochromer]: https://github.com/monochromer +[Teletha]: https://github.com/teletha +[Nicolas Stucki]: https://github.com/nicolasstucki +[Bahnschrift]: https://github.com/Bahnschrift +[Melvyn Laïly]: https://github.com/mlaily +[katzeprior]: https://github.com/katzeprior +[zsoltlengyelit]: github.com/zsoltlengyelit +[Syb Wartna]:https://github.com/waarissyb +[idleberg]: https://github.com/idleberg +[ierehon1905]: https://github.com/ierehon1905 + + +## Version 11.2.0 + +Build: + +- fix: run Node build CSS files thru CSS processor also (#3284) [Josh Goebel][] + +Parser: + +- fix(csharp) Fix assignments flagging as functions [Josh Goebel][] +- fix(types) Fix some type definition issues (#3274) [Josh Goebel][] +- fix(verilog) Fix directive handling (#3283) [Josh Goebel][] +- fix(verilog) Fix binary number false positives on `_` (#3283) [Josh Goebel][] +- enh(verilog) `__FILE__` and `__LINE__` constants (#3283) [Josh Goebel][] +- enh(verilog) tighten keyword regex (#3283) [Josh Goebel][] + + +Grammars: + +- enh(swift) Add `isolated`/`nonisolated` keywords (#3296) [Bradley Mackey][] + +New Languages: + +- Added 3rd party X# grammar to SUPPORTED_LANGUAGES [Patrick Kruselburger][] +- Added 3rd party MKB grammar to SUPPORTED_LANGUAGES (#3297) [Dereavy][] + +[Josh Goebel]: https://github.com/joshgoebel +[Patrick Kruselburger]: https://github.com/PatrickKru +[Bradley Mackey]: https://github.com/bradleymackey +[Dereavy]: https://github.com/dereavy + + +## Version 11.1.0 + +Grammars: + +- fix(csharp) add missing `catch` keyword (#3251) [Konrad Rudolph][] +- add additional keywords to csp.js (#3244) [Elijah Conners][] +- feat(css) handle css variables syntax (#3239) [Thanos Karagiannis][] +- fix(markdown) Images with empty alt or links with empty text (#3233) [Josh Goebel][] +- enh(powershell) added `pwsh` alias (#3236) [tebeco][] +- fix(r) fix bug highlighting examples in doc comments [Konrad Rudolph][] +- fix(python) identifiers starting with underscore not highlighted (#3221) [Antoine Lambert][] +- enh(clojure) added `edn` alias (#3213) [Stel Abrego][] +- enh(elixir) much improved regular expression sigil support (#3207) [Josh Goebel][] +- enh(elixir) updated list of keywords (#3212) [Angelika Tyborska][] +- fix(elixir) fixed number detection when numbers start with a zero (#3212) [Angelika Tyborska][] +- fix(ps1) Flag highlighted incorrectly (#3167) [Pankaj Patil][] +- fix(latex) Allow wider syntax for magic comments (#3243) [Benedikt Wilde][] +- fix(js/ts) Constants may include numbers [Josh Goebel][] + +[Stel Abrego]: https://github.com/stelcodes +[Josh Goebel]: https://github.com/joshgoebel +[Antoine Lambert]: https://github.com/anlambert +[Elijah Conners]: https://github.com/elijahepepe +[Angelika Tyborska]: https://github.com/angelikatyborska +[Konrad Rudolph]: https://github.com/klmr +[tebeco]: https://github.com/tebeco +[Pankaj Patil]: https://github.com/patil2099 +[Benedikt Wilde]: https://github.com/schtandard +[Thanos Karagiannis]: https://github.com/thanoskrg + + +## Version 11.0.0 + +**This is a major release.** As such it contains breaking changes which may require action from users. Please read [VERSION_11_UPGRADE.md](https://github.com/highlightjs/highlight.js/blob/main/VERSION_11_UPGRADE.md) for a detailed summary of all breaking changes. + +### Potentially breaking changes + +Unless otherwise attributed items below are thanks to [Josh Goebel][] (ref: [#2558](https://github.com/highlightjs/highlight.js/issues/2558)). + +*The below list should only be considered to be a high-level summary.* + +Deprecations / Removals / API Changes: + +- `initHighlighting()` and `initHighlightingOnLoad()` deprecated. **Use `highlightAll()`.** +- `highlightBlock(el)` deprecated. **Use `highlightElement(el)`** +- `before:highlightBlock` & `after:highlightBlock` callbacks deprecated. **Use equivalent `highlightElement` callbacks.** +- `highlight(languageName, code, ignoreIllegals, continuation)` signature deprecated. **Use `highlight(code, {language, ignoreIllegals})`.** +- Deprecated `highlight()` signature no longer supports `continuation` argument. +- `tabReplace` option removed. Consider a plugin. +- `useBR` option removed. Consider a plugin or CSS. +- `requireLanguage()` removed. **Use `getLanguage()`.** +- `endSameAsBegin` mode key removed. **Use `hljs.END_SAME_AS_BEGIN`.** +- `lexemes` mode key removed. **Use `keywords.$pattern`.** +- The return values/keys of some APIs have changed slightly. + +Security: + +- HTML auto-passthru has been removed. Consider a plugin. +- Unescaped HTML is now stripped (for security). A warning is logged to the console. (#3057) [Josh Goebel][] + +Themes: + +- The default padding of all themes increases (0.5em => 1em). +- `schoolbook` has been updated to remove the lined background. +- `github` updated to better match modern GitHub (#1616) [Jan Pilzer][] +- `github-gist` has been removed in favor of `github` [Jan Pilzer][] +- Base16 named themes have been updated to their "canonical" versions +- `nnfx` updated for v11 xml styles and improved css support + +Language Grammars: + +- Default CDN build drops support for several languages. +- Some language grammar files have been removed. +- Some redundant language aliases have been removed. + +### Other changes + +Parser: + +- enh(vala) improve language detection for Vala (#3195) [Konrad Rudolph][] +- enh(r) add support for operators, fix number highlighting bug (#3194, #3195) [Konrad Rudolph][] +- enh(parser) add `beginScope` and `endScope` to allow separate scoping begin and end (#3159) [Josh Goebel][] +- enh(parsed) `endScope` now supports multi-class matchers as well (#3159) [Josh Goebel][] +- enh(parser) `highlightElement` now always tags blocks with a consistent `language-[name]` class [Josh Goebel][] + - subLanguage `span` tags now also always have the `language-` prefix added +- enh(parser) support multi-class matchers (#3081) [Josh Goebel][] +- enh(parser) Detect comments based on english like text, rather than keyword list [Josh Goebel][] +- adds `title.class.inherited` sub-scope support [Josh Goebel][] +- adds `title.class` sub-scope support (#3078) [Josh Goebel][] +- adds `title.function` sub-scope support (#3078) [Josh Goebel][] +- adds `beforeMatch` compiler extension (#3078) [Josh Goebel][] +- adds `cssSelector ` configuration option (#3180) [James Edington][] + +Grammars: + +- enh(all) `.meta-keyword` => `.meta .keyword` (nested scopes) (#3167) [Josh Goebel][] +- enh(all) `.meta-string` => `.meta .string` (nested scopes) (#3167) [Josh Goebel][] +- enh(swift) add `actor` keyword (#3171) [Bradley Mackey][] +- enh(crystal) highlight variables (#3154) [Josh Goebel][] +- fix(ruby) Heredoc without interpolation (#3154) [Josh Goebel][] +- enh(swift) add `@resultBuilder` attribute (#3151) [Bradley Mackey][] +- enh(processing) added `pde` alias (#3142) [Dylan McBean][] +- enh(thrift) Use proper scope for types [Josh Goebel][] +- enh(java) Simplified class-like matcher (#3078) [Josh Goebel][] +- enh(cpp) Simplified class-like matcher (#3078) [Josh Goebel][] +- enh(rust) Simplified class-like matcher (#3078) [Josh Goebel][] +- enh(actionscript) Simplified class-like matcher (#3078) [Josh Goebel][] +- enh(arcade) `function.title` => `title.function` (#3078) [Josh Goebel][] +- enh(autoit) `function.title` => `title.function` (#3078) [Josh Goebel][] +- enh(c) `function.title` => `title.function` (#3078) [Josh Goebel][] +- enh(rust) support function invoke and `impl` (#3078) [Josh Goebel][] +- chore(properties) disable auto-detection #3102 [Josh Goebel][] +- fix(properties) fix incorrect handling of non-alphanumeric keys #3102 [Egor Rogov][] +- enh(java) support functions with nested template types (#2641) [Josh Goebel][] +- enh(java) highlight types and literals separate from keywords (#3074) [Josh Goebel][] +- enh(shell) add alias ShellSession [Ryan Mulligan][] +- enh(shell) consider one space after prompt as part of prompt [Ryan Mulligan][] +- fix(nginx) fix bug with $ and @ variables [Josh Goebel][] +- enh(nginx) improving highlighting of some sections [Josh Goebel][] +- fix(vim) variable names may not be zero length [Josh Goebel][] +- enh(sqf) Updated keywords to Arma 3 v2.02 (#3084) [R3voA3][] +- enh(sqf) Refactored function regex to match CBA component func naming scheme (#3181) [JonBons][] +- enh(nim) highlight types properly (not as built-ins) [Josh Goebel][] +- (chore) throttle deprecation messages (#3092) [Mihkel Eidast][] +- enh(c) Update keyword list for C11/C18 (#3010) [Josh Goebel][] +- enh(parser) highlight object properties (#3072) [Josh Goebel][] +- enh(javascript/typescript) highlight object properties (#3072) [Josh Goebel][] +- enh(haskell) add support for BinaryLiterals (#3150) [Martijn Bastiaan][] +- enh(haskell) add support for NumericUnderscores (#3150) [Martijn Bastiaan][] +- enh(haskell) add support for HexFloatLiterals (#3150) [Martijn Bastiaan][] +- fix(c,cpp) allow declaring multiple functions and (for C++) parenthetical initializers (#3155) [Erik Demaine][] +- enh(rust) highlight raw byte string literals correctly (#3173) [Nico Abram][] +- fix(cpp) fix detection of common functions that are function templates (#3178) [Kris van Rens][] +- enh(cpp) add various keywords and commonly used types for hinting (#3178) [Kris van Rens][] +- enh(cpp) cleanup reserved keywords and type lists (#3178) [Kris van Rens][] + +New Languages: + +- Added 3rd party Glimmer grammar to SUPPORTED_LANGUAGES(#3123) [NullVoxPopuli][] +- Added Wren support [Josh Goebel][] +- Added NestedText support [Josh Goebel][] +- Added WebAssembly language grammar [Josh Goebel][] +- Added 3rd party Splunk search processing language grammar to SUPPORTED_LANGUAGES (#3090) [Wei Su][] +- Added 3rd party ZenScript grammar to SUPPORTED_LANGUAGES(#3106) [Jared Luboff][] +- Added 3rd party Papyrus grammar to SUPPORTED_LANGUAGES(#3125) [Mike Watling][] + +Theme Improvements: + +- Added all official Base16 themes (over 150 new themes) [Josh Goebel][] +- chore(themes) remove `builtin-name` CSS class (#3119) [Josh Goebel][] +- chore(theme) Update GitHub theme css to match GitHub's current styling (#1616) [Jan Pilzer][] +- chore(theme) Update Srcery theme css to match its Emacs implementation [Chen Bin][] + +New Themes: + +- DeviBeans Dark by [Farzad Sadeghi][] +- GitHub Dark and GitHub Dark Dimmed [Jan Pilzer][] + +Dev Improvements: + +- (chore) greatly improve match scope visualization in dev tool (#3126) [NullVoxPopuli][] +- (fix) CSS used for devtool needed an adjustment to fix too wide of content (#3133) [NullVoxPopuli][] + +[Farzad Sadeghi]: https://github.com/terminaldweller +[Martijn Bastiaan]: https://github.com/martijnbastiaan +[Bradley Mackey]: https://github.com/bradleymackey +[Dylan McBean]: https://github.com/DylanMcBean +[Josh Goebel]: https://github.com/joshgoebel +[Ryan Mulligan]: https://github.com/ryantm +[R3voA3]: https://github.com/R3voA3 +[JonBons]: https://github.com/JonBons +[Wei Su]: https://github.com/swsoyee +[Jared Luboff]: https://github.com/jaredlll08 +[NullVoxPopuli]: https://github.com/NullVoxPopuli +[Mike Watling]: https://github.com/Pickysaurus +[Nico Abram]: https://github.com/nico-abram +[James Edington]: http://www.ishygddt.xyz/ +[Jan Pilzer]: https://github.com/Hirse +[Kris van Rens]: https://github.com/krisvanrens + + +## Version 10.7.1 + +- fix(parser) Resolves issues with TypeScript types [Josh Goebel][] + +### Version 10.7.0 + +Parser: + +- keywords now have a maximum # of times they provide relevance (#3129) [Josh Goebel][] +- enh(api) add `unregisterLanguage` method (#3009) [Antoine du Hamel][] +- enh: Make alias registration case insensitive (#3026) [David Ostrovsky][] +- fix(parser) `highlightAll()` now works if the library is lazy loaded [Josh Goebel][] + +New Languages: + +- Added 3rd party RiScript grammar to SUPPORTED_LANGUAGES (#2988) [John C][] +- Added 3rd party HLSL grammar to SUPPORTED_LANGUAGES (#3002) [Stef Levesque][] +- Added 3rd party Q# grammar to SUPPORTED_LANGUAGES(#3006) [Vyron Vasileiadis][] + +Language grammar improvements: + +- enh(js/ts) class references (CamelCase) are highlighted (#3169) [Josh Goebel][] +- enh(js/ts) constants (ALL_CAPS) are highlighted (#3169) [Josh Goebel][] +- enh(js/ts) highlights function invokation (#3169) [Josh Goebel][] +- enh(js/ts) functions assigned to variables are now highlighted `title.function` (#3169) [Josh Goebel][] +- enh(parser) smarter detection of comments (#2827) [Josh Goebel][] +- fix(python) allow keywords immediately following numbers (#2985) [Josh Goebel][] +- fix(xml) char immediately following tag close mis-highlighted (#3044) [Josh Goebel][] +- fix(ruby) fix `defined?()` mis-highlighted as `def` (#3025) [Josh Goebel][] +- fix(c) comments after `#include ` blocks (#3041) [Josh Goebel][] +- fix(cpp) comments after `#include ` blocks (#3041) [Josh Goebel][] +- enh(cpp) Highlight all function dispatches (#3005) [Josh Goebel][] +- enh(python) support type hints and better type support (#2972) [Josh Goebel][] +- enh(gml) Add additional GML 2.3 keywords (#2984) [xDGameStudios][] +- fix(cpp) constructor support for initializers (#3001) [Josh Goebel][] +- enh(php) Add `trait` to class-like naming patterns (#2997) [Ayesh][] +- enh(php) Add `Stringable`, `UnhandledMatchError`, and `WeakMap` classes/interfaces (#2997) [Ayesh][] +- enh(php) Add `mixed` to list of keywords (#2997) [Ayesh][] +- enh(php) Add support binary, octal, hex and scientific numerals with underscore separator support (#2997) [Ayesh][] +- enh(php) Add support for Enums (#3004) [Ayesh][] +- enh(ecmascript) Add built-in types [Vaibhav Chanana][] +- enh(kotlin) Add `kts` as an alias for Kotlin (#3021) [Vaibhav Chanana][] +- enh(css) Add `font-smoothing` to attributes list for CSS (#3027) [AndyKIron][] +- fix(python) Highlight `print` and `exec` as a builtin (#1468) [Samuel Colvin][] +- fix(csharp) Fix unit being highlighted instead of uint (#3046) [Spacehamster][] +- enh(swift) add async/await keywords (#3048) [Bradley Mackey][] + +Deprecations: + +- `highlight(languageName, code, ignoreIllegals, continuation)` deprecated as of 10.7 + - Please use the newer API which takes `code` and then accepts options as an object + - IE: `highlight(code, {language, ignoreIllegals})` + - `continuation` is for internal use only and no longer supported +- `highlightBlock(el)` deprecated as of 10.7. + - Please use `highlightElement(el)` instead. + - Plugin callbacks renamed `before/after:highlightBlock` => `before/after:highlightElement` + - Plugin callback now takes `el` vs `block` attribute + - The old API and callbacks will be supported until v12. + + +[Stef Levesque]: https://github.com/stef-levesque +[Josh Goebel]: https://github.com/joshgoebel +[John Cheung]: https://github.com/Real-John-Cheung +[xDGameStudios]: https://github.com/xDGameStudios +[Ayesh]: https://github.com/Ayesh +[Vyron Vasileiadis]: https://github.com/fedonman +[Antoine du Hamel]: https://github.com/aduh95 +[Vaibhav Chanana]: https://github.com/il3ven +[David Ostrovsky]: https://github.com/davido +[AndyKIron]: https://github.com/AndyKIron +[Samuel Colvin]: https://github.com/samuelcolvin + +## Version 10.6.0 + +New Languages: + +- Added 3rd party Laravel Blade grammar to SUPPORTED_LANGUAGES (#2944) [Michael Newton][] + +Language grammar improvements: + +- enh(scala) fix triple quoted strings (#2987) [Josh Goebel][] +- enh(perl) Much improved regex detection (#2960) [Josh Goebel][] +- enh(swift) Improved highlighting for operator and precedencegroup declarations. (#2938) [Steven Van Impe][] +- fix(xml) Support single-character namespaces. (#2957) [Jan Pilzer][] +- enh(ruby) Support for character literals (#2950) [Vaibhav Chanana][] +- enh(powershell) Add three VALID_VERBS and update the reference link (#2981) [davidhcefx][] +- fix(php) Highlighting of anonymous functions without {} block [Vaibhav Chanana][] + +Grammar Deprecations: + +- Deprecate `c-like`, though you should not be using it directly anyways. + - will be removed in v11. +- `c` and `cpp` are now wholly unique grammars that will diverge over time + +Parser: + +- new simpler `highlightAll()` API (#2962) [Josh Goebel][] + - this should be a drop-in replacement for both `initHighlighting()` and `initHighlightingOnLoad()` + - note: it does not prevent itself from being called multiple times (as the previous API did) +- `beginKeyword` no longer bestows double relevance (#2953) [Josh Goebel][] +- allow `keywords` to be an array of strings [Josh Goebel][] +- add `modes.MATCH_NOTHING_RE` that will never match + - This can be used with `end` to hold a mode open (it must then be ended with `endsParent` in one of it's children modes) [Josh Goebel][] + +Deprecations: + +- `initHighlighting()` and `initHighlightingOnLoad()` deprecated. + - Please use the new `highlightAll()` API instead. + - Deprecated as of 10.6. + - These will both be aliases to `highlightAll` in v11. + +[Michael Newton]: https://github.com/miken32 +[Steven Van Impe]: https://github.com/svanimpe/ +[Josh Goebel]: https://github.com/joshgoebel +[Vaibhav Chanana]: https://github.com/il3ven +[davidhcefx]: https://github.com/davidhcefx +[Jan Pilzer]: https://github.com/Hirse + + +## Version 10.5.0 + +Build: + +- Add Subresource Integrity digest lists to `cdn-assets` [Josh Goebel][] +- R and VB.net grammars now ship in our default build (`:common`) [Josh Goebel][] + +Parser: + +- add `match` as sugar for simple `begin` only matches (#2834) [Josh Goebel][] +- allow `illegal` to also be an array of regex (#2834) [Josh Goebel][] +- add `compilerExtensions` allows grammers to influence mode compilation (#2834) [Josh Goebel][] + - some internal pieces are now simple compiler extensions + +New Languages: + +- Added 3rd party Red & Rebol grammar to SUPPORTED_LANGUAGES (#2872) [Oldes Huhuman][] + +Language grammar improvements: + +- enh: CSS grammars now share common foundation, keywords, etc. (#2937) [Josh Goebel][] + - enh(css): many consistency improvements + - enh(scss): many consistency improvements + - enh(stylus): many consistency improvements + - enh(less): many consistency improvements +- enh(cpp): Support C++ pack expansion in function arguments [Martin Dørum][] +- enh(makefile): Add `make` as an alias (#2883) [tripleee][] +- enh(swift) Improved grammar for strings (#2819) [Steven Van Impe][] +- enh(swift) Grammar improvements (#2908) [Steven Van Impe][] + - New grammar for keywords and built-ins + - Added support for operator highlighting + - New grammar for attributes + - Added support for quoted identifiers, implicit parameters, and property wrapper projections + - Support for more complex expressions in string interpolation +- enh(swift) Improved highlighting for types and generic arguments (#2920) [Steven Van Impe][] +- enh(swift) Improved highlighting for functions, initializers, and subscripts (#2930) [Steven Van Impe][] +- fix(http) avoid recursive sublanguage and tighten rules (#2893) [Josh Goebel][] +- fix(asciidoc): Handle section titles level 5 (#2868) [Vaibhav Chanana][] +- fix(asciidoc): Support unconstrained emphasis syntax (#2869) [Guillaume Grossetie][] +- enh(scheme) Allow `[]` for argument lists (#2913) [Josh Goebel][] +- enh(vb) Large rework of VB.net grammar (#2808) [Jan Pilzer][] + - Adds support for Date data types, see (#2775) + - Adds support for `REM` comments and fixes `'''` doctags (#2875) (#2851) + - Custom number mode to support VB.net specific number flags + - Hex (&H), Oct (&O), and binary (&B) prefixes + - Separating digits with underscores: 90_946 + - Type suffixes: 123UI (unsigned integer) + - Improves directives detection and adds support for `Enable`, `Disable`, and `Then` keywords + - Adds more markup tests +- fix(javascript) Empty block-comments break highlighting (#2896) [Jan Pilzer][] +- enh(dart) Fix empty block-comments from breaking highlighting (#2898) [Jan Pilzer][] +- enh(dart) Fix empty doc-comment eating next line [Jan Pilzer][] +- enh(asciidoc) Adds support for unconstrained bold syntax (#2869) [Guillaume Grossetie][] +- enh(c-like) Incorrect highlighting for interger suffix (#2919) [Vaibhav Chanana][] +- enh(properties) Correctly handle trailing backslash (#2922) [Vaibhav Chanana][] + +Recent Deprecations: + +- HTML "merging" is deprecated. (#2873) [Josh Goebel][] + - HTML inside `
` blocks will no longer be magically merged back into the
+  highlighted code's HTML result - it will instead be silently removed.
+  - Consider [using a plugin][htmlPlugin] if you truly need this functionality
+  - Deprecated as of 10.5.0 - will be removed in v11.
+- `tabReplace` option deprecated. (#2873) [Josh Goebel][]
+  - **Consider:** Use the CSS `tab-size` property, or simply pre-process the
+    text yourself before rendering the initial HTML
+  - otherwise, [use a plugin][tabPlugin]
+  - Deprecated as of 10.5.0 - will be removed in v11.
+- `useBR` option deprecated. (#2559) [Josh Goebel][]
+  - **Recommended:** You really should just use the HTML `
` tag
+  - or perhaps try CSS `white-space: pre;`
+  - otherwise, [use a plugin][brPlugin]
+  - Deprecated as of 10.3.0 - will be removed in v11.
+- `requireLanguage` API is deprecated, will be removed in v11.0.
+  - **Consider:** Use `getLanguage` (with custom error handling) or built-time dependencies.
+  - See [Library API](https://highlightjs.readthedocs.io/en/latest/api.html#requirelanguage-name) for more information.
+  - Deprecated as of 10.4.0 - will be removed in v11.
+
+[htmlPlugin]: https://github.com/highlightjs/highlight.js/issues/2889
+[tabPlugin]: https://github.com/highlightjs/highlight.js/issues/2874
+[brPlugin]: https://github.com/highlightjs/highlight.js/issues/2559
+
+[Martin Dørum]: https://github.com/mortie
+[Jan Pilzer]: https://github.com/Hirse
+[Oldes Huhuman]: https://github.com/Oldes
+[Josh Goebel]: https://github.com/joshgoebel
+[tripleee]: https://github.com/tripleee
+[Steven Van Impe]: https://github.com/svanimpe/
+[Vaibhav Chanana]: https://github.com/il3ven
+[Guillaume Grossetie]: https://github.com/mogztter
+
+
+## Version 10.4.1 (tentative)
+
+Security
+
+- (fix) Exponential backtracking fixes for: [Josh Goebel][]
+  - cpp
+  - handlebars
+  - gams
+  - perl
+  - jboss-cli
+  - r
+  - erlang-repl
+  - powershell
+  - routeros
+- (fix) Polynomial backtracking fixes for: [Josh Goebel][]
+  - asciidoc
+  - reasonml
+  - latex
+  - kotlin
+  - gcode
+  - d
+  - aspectj
+  - moonscript
+  - coffeescript/livescript
+  - csharp
+  - scilab
+  - crystal
+  - elixir
+  - basic
+  - ebnf
+  - ruby
+  - fortran/irpf90
+  - livecodeserver
+  - yaml
+  - x86asm
+  - dsconfig
+  - markdown
+  - ruleslanguage
+  - xquery
+  - sqf
+
+Very grateful to [Michael Schmidt][] for all the help.
+
+[Michael Schmidt]: https://github.com/RunDevelopment
+[Josh Goebel]: https://github.com/joshgoebel
+
+
+## Version 10.4.0
+
+A largish release with many improvements and fixes from quite a few different contributors.  Enjoy!
+
+Deprecations:
+
+- (chore) `requireLanguage` is deprecated.
+  - Prefer `getLanguage` (with custom error handling) or built-time dependencies.
+  - See [Library API](https://highlightjs.readthedocs.io/en/latest/api.html#requirelanguage-name) for more information.
+
+Parser:
+
+- enh(parser) use negative look-ahead for `beginKeywords` support (#2813) [Josh Goebel][]
+- enh(grammars) allow `classNameAliases` for more complex grammars [Josh Goebel][]
+- fix(vue): Language name now appears in CSS class (#2807) [Michael Rush][]
+- (chore) Clean up all regexs to be UTF-8 compliant/ready (#2759) [Josh Goebel][]
+- enh(grammars) allow `classNameAliases` for more complex grammars [Josh Goebel][]
+
+New Languages:
+
+- Added 3rd party Chapel grammar to SUPPORTED_LANGUAGES (#2806) [Brad Chamberlain][]
+- Added BBCode grammar to SUPPORTED_LANGUAGES (#2867) [Paul Reid][]
+- enh(javascript) Added `node-repl` for Node.js REPL sessions (#2792) [Marat Nagayev][]
+
+Language Improvements:
+
+- enh(shell) Recognize prompts which contain tilde `~` (#2859) [Guillaume Grossetie][]
+- enh(shell) Add support for multiline commands with line continuation `\` (#2861) [Guillaume Grossetie][]
+- enh(autodetect) Over 30+ improvements to auto-detect (#2745) [Josh Goebel][]
+    - 4-5% improvement in auto-detect against large sample set
+    - properties, angelscript, lsl, javascript, n1ql, ocaml, ruby
+    - protobuf, hy, scheme, crystal, yaml, r, vbscript, groovy
+    - python, java, php, lisp, matlab, clojure, csharp, css
+- fix(r) fixed keywords not properly spaced (#2852) [Josh Goebel][]
+- fix(javascript) fix potential catastrophic backtracking (#2852) [Josh Goebel][]
+- fix(livescript) fix potential catastrophic backtracking (#2852) [Josh Goebel][]
+- bug(xml) XML grammar was far too imprecise/fuzzy [Josh Goebel][]
+- enh(xml) Improve precision to prevent false auto-detect positives [Josh Goebel][]
+- fix(js/ts) Prevent for/while/if/switch from falsly matching as functions (#2803) [Josh Goebel][]
+- enh(julia) Update keyword lists for Julia 1.x (#2781) [Fredrik Ekre][]
+- enh(python) Match numeric literals per the language reference [Richard Gibson][]
+- enh(ruby) Match numeric literals per language documentation [Richard Gibson][]
+- enh(javascript) Match numeric literals per ECMA-262 spec [Richard Gibson][]
+- enh(java) Match numeric literals per Java Language Specification [Richard Gibson][]
+- enh(swift) Match numeric literals per language reference [Richard Gibson][]
+- enh(php) highlight variables (#2785) [Taufik Nurrohman][]
+- fix(python) Handle comments on decorators (#2804) [Jonathan Sharpe][]
+- enh(diff) improve highlighting of diff for git patches [Florian Bezdeka][]
+- fix(llvm) lots of small improvements and fixes (#2830) [Josh Goebel][]
+- enh(mathematica) Rework entire implementation [Patrick Scheibe][]
+  - Correct matching of the many variations of Mathematica's numbers
+  - Matching of named-characters aka special symbols like `\[Gamma]`
+  - Updated list of version 12.1 built-in symbols
+  - Matching of patterns, slots, message-names and braces
+- fix(swift) Handle keywords that start with `#` [Marcus Ortiz][]
+- enh(swift) Match `some` keyword [Marcus Ortiz][]
+- enh(swift) Match `@main` attribute [Marcus Ortiz][]
+
+Dev Improvements:
+
+- chore(dev) add theme picker to the tools/developer tool (#2770) [Josh Goebel][]
+- fix(dev) the Vue.js plugin no longer throws an exception when hljs is not in the global namespace [Kyle Brown][]
+
+New themes:
+
+- *StackOverflow Dark* by [Jan Pilzer][]
+- *StackOverflow Light* by [Jan Pilzer][]
+
+[Guillaume Grossetie]: https://github.com/mogztter
+[Brad Chamberlain]: https://github.com/bradcray
+[Marat Nagayev]: https://github.com/nagayev
+[Fredrik Ekre]: https://github.com/fredrikekre
+[Richard Gibson]: https://github.com/gibson042
+[Josh Goebel]: https://github.com/joshgoebel
+[Taufik Nurrohman]: https://github.com/taufik-nurrohman
+[Jan Pilzer]: https://github.com/Hirse
+[Jonathan Sharpe]: https://github.com/textbook
+[Michael Rush]: https://github.com/rushimusmaximus
+[Patrick Scheibe]: https://github.com/halirutan
+[Kyle Brown]: https://github.com/kylebrown9
+[Marcus Ortiz]: https://github.com/mportiz08
+[Paul Reid]: https://github.com/RedGuy12
+
+
+## Version 10.3.1
+
+Prior version let some look-behind regex sneak in, which does not work
+yet on Safari.  This release removes those incompatible regexes.
+
+Fix:
+
+- fix(Safari) Remove currently unsupported look-behind regex ([fix][187e7cfc]) [Josh Goebel][]
+
+[Josh Goebel]: https://github.com/joshgoebel
+[187e7cfc]: https://github.com/highlightjs/highlight.js/commit/187e7cfcb06277ce13b5f35fb6c37ab7a7b46de9
+
+
+## Version 10.3.0
+
+Language Improvements:
+
+- enh(latex) Complete ground up rewrite of LaTex grammar [schtandard][]
+- fix(cpp) implement backslash line continuation in comments (#2757) [Konrad Rudolph][]
+- fix(cpp) improve parsing issues with templates (#2752) [Josh Goebel][]
+- enh(cpp) add support for `enum (struct|class)` and `union` (#2752) [Josh Goebel][]
+- fix(js/ts) Fix nesting of `{}` inside template literals SUBST expression (#2748) [Josh Goebel][]
+- enh(js/ts) Highlight class methods as functions (#2727) [Josh Goebel][]
+- fix(js/ts) `constructor` is now highlighted as a function title (not keyword) (#2727) [Josh Goebel][]
+- fix(c-like) preprocessor directives not detected after else (#2738) [Josh Goebel][]
+- enh(javascript) allow `#` for private class fields (#2701) [Chris Krycho][]
+- fix(js) prevent runaway regex (#2746) [Josh Goebel][]
+- fix(bash) enh(bash) allow nested params (#2731) [Josh Goebel][]
+- fix(python) Fix highlighting of keywords and strings (#2713, #2715) [Konrad Rudolph][]
+- fix(fsharp) Prevent `(*)` from being detected as a multi-line comment [Josh Goebel][]
+- enh(bash) add support for heredocs (#2684) [Josh Goebel][]
+- enh(r) major overhaul of the R language grammar (and fix a few bugs) (#2680) [Konrad Rudolph][]
+- enh(csharp) Add all C# 9 keywords, and other missing keywords (#2679) [David Pine][]
+- enh(objectivec) Add `objective-c++` and `obj-c++` aliases for Objective-C [Josh Goebel][]
+- enh(java) Add support for `record` (#2685) [Josh Goebel][]
+- fix(csharp) prevent modifier keywords wrongly flagged as `title` (#2683) [Josh Goebel][]
+- enh(axapta) Update keyword list for Axapta (X++) (#2686) [Ryan Jonasson][]
+- fix(fortran) FORTRAN 77-style comments (#2677) [Philipp Engel][]
+- fix(javascript) Comments inside params should be highlighted (#2702) [Josh Goebel][]
+- fix(scala) Comments inside class header should be highlighted (#1559) [Josh Goebel][]
+- fix(c-like) Correctly highlight modifiers (`final`) in class declaration (#2696) [Josh Goebel][]
+- enh(angelscript) Improve heredocs, numbers, metadata blocks (#2724) [Melissa Geels][]
+- enh(javascript) Implement Numeric Separators (#2617) [Antoine du Hamel][]
+- enh(typescript) TypeScript also gains support for numeric separators (#2617) [Antoine du Hamel][]
+- enh(php) Add support for PHP 8 `match` keyword and add `php8` as an alias (#2733) [Ayesh Karunaratne][]
+- fix(handlebars) Support if else keyboards (#2659) [Tom Wallace][]
+
+Deprecations:
+
+- `useBR` option deprecated and will be removed in v11.0. (#2559) [Josh Goebel][]
+
+[Chris Krycho]: https://github.com/chriskrycho
+[David Pine]: https://github.com/IEvangelist
+
+
+[Ryan Jonasson]: https://github.com/ryanjonasson
+[Philipp Engel]: https://github.com/interkosmos
+[Konrad Rudolph]: https://github.com/klmr
+[Melissa Geels]: https://github.com/codecat
+[Antoine du Hamel]: https://github.com/aduh95
+[Ayesh Karunaratne]: https://github.com/Ayesh
+[Tom Wallace]: https://github.com/thomasmichaelwallace
+[schtandard]: https://github.com/schtandard
+
+
+## Version 10.2.1
+
+ Parser Engine:
+
+ -  fix(parser) complete fix for resuming matches from same index (#2678) [Josh Goebel][]
+
+ [Josh Goebel]: https://github.com/yyyc514
+
+
+## Version 10.2.0
+
+Parser Engine:
+
+- (fix) When ignoring a potential match highlighting can terminate early (#2649) [Josh Goebel][]
+
+
+New themes:
+
+- *Gradient Light* by [Samia Ali]()
+
+Deprecations:
+
+- `fixMarkup` is now deprecated and will be removed in v11.0. (#2534) [Josh Goebel][]
+
+Big picture:
+
+- Add simple Vue plugin for basic use cases (#2544) [Josh Goebel][]
+
+Language Improvements:
+
+- fix(bash) Fewer false positives for keywords in arguments (#2669) [sirosen][]
+- fix(js) Prevent long series of /////// from causing freezes (#2656) [Josh Goebel][]
+- enh(csharp) Add `init` and `record` keywords for C# 9.0 (#2660) [Youssef Victor][]
+- enh(matlab) Add new R2019b `arguments` keyword and fix `enumeration` keyword (#2619) [Andrew Janke][]
+- fix(kotlin) Remove very old keywords and update example code (#2623) [kageru][]
+- fix(night) Prevent object prototypes method values from being returned in `getLanguage` (#2636) [night][]
+- enh(java) Add support for `enum`, which will identify as a `class` now (#2643) [ezksd][]
+- enh(nsis) Add support for NSIS 3.06 commands (#2653) [idleberg][]
+- enh(php) detect newer more flexible HEREdoc syntax (#2658) [eytienne][]
+
+[Youssef Victor]: https://github.com/Youssef1313
+[Josh Goebel]: https://github.com/joshgoebel
+[Andrew Janke]: https://github.com/apjanke
+[Samia Ali]: https://github.com/samiaab1990
+[kageru]: https://github.com/kageru
+[night]: https://github.com/night
+[ezksd]: https://github.com/ezksd
+[idleberg]: https://github.com/idleberg
+[eytienne]: https://github.com/eytienne
+[sirosen]: https://github.com/sirosen
+
+## Version 10.1.1
+
+Fixes:
+
+- Resolve issue on Node 6 due to dangling comma (#2608) [Edwin Hoogerbeets][]
+- Resolve `index.d.ts is not a module` error (#2603) [Josh Goebel][]
+
+[Josh Goebel]: https://github.com/joshgoebel
+[Edwin Hoogerbeets]: https://github.com/ehoogerbeets
+
+
+## Version 10.1.0
+
+New themes:
+
+- *NNFX* and *NNFX-dark* by [Jim Mason][]
+- *lioshi* by [lioshi][]
+
+Parser Engine:
+
+- (parser) Now escapes quotes in text content when escaping HTML (#2564) [Josh Goebel][]
+- (parser) Adds `keywords.$pattern` key to grammar definitions (#2519) [Josh Goebel][]
+- (parser) Adds SHEBANG utility mode [Josh Goebel][]
+- (parser) Adds `registerAliases` method (#2540) [Taufik Nurrohman][]
+- (enh) Added `on:begin` callback for modes (#2261) [Josh Goebel][]
+- (enh) Added `on:end` callback for modes (#2261) [Josh Goebel][]
+- (enh) Added ability to programatically ignore begin and end matches (#2261) [Josh Goebel][]
+- (enh) Added `END_SAME_AS_BEGIN` mode to replace `endSameAsBegin` parser attribute (#2261) [Josh Goebel][]
+- (fix) `fixMarkup` would rarely destroy markup when `useBR` was enabled (#2532) [Josh Goebel][]
+
+Deprecations:
+
+- `htmlbars` grammar is now deprecated. Use `handlebars` instead. (#2344) [Nils Knappmeier][]
+- when using `highlightBlock` `result.re` deprecated. Use `result.relevance` instead. (#2552) [Josh Goebel][]
+- ditto for `result.second_best.re` => `result.second_best.relevance` (#2552)
+- `lexemes` is now deprecated in favor of `keywords.$pattern` key (#2519) [Josh Goebel][]
+- `endSameAsBegin` is now deprecated. (#2261) [Josh Goebel][]
+
+Language Improvements:
+
+- fix(groovy) strings are not allowed inside ternary clauses (#2217) [Josh Goebel][]
+- fix(typescript) add `readonly` keyword (#2562) [Martin (Lhoerion)][]
+- fix(javascript) fix regex inside parens after a non-regex (#2530) [Josh Goebel][]
+- enh(typescript) use identifier to match potential keywords, preventing false positivites (#2519) [Josh Goebel][]
+- enh(javascript) use identifier to match potential keywords, preventing false positivites (#2519) [Josh Goebel][]
+- [enh] Add `OPTIMIZE:` and `HACK:` to the labels highlighted inside comments [Josh Goebel][]
+- enh(typescript/javascript/coffeescript/livescript) derive ECMAscript keywords from a common foudation (#2518) [Josh Goebel][]
+- enh(typescript) add setInterval, setTimeout, clearInterval, clearTimeout (#2514) [Josh Goebel][]
+- enh(javascript) add setInterval, setTimeout, clearInterval, clearTimeout (#2514) [Vania Kucher][]
+- enh(cpp) add `pair`, `make_pair`, `priority_queue` as built-ins (#2538) [Hankun Lin][]
+- enh(cpp) recognize `priority_queue` `pair` as cpp containers (#2541) [Hankun Lin][]
+- fix(javascript) prevent `set` keyword conflicting with setTimeout, etc. (#2514) [Vania Kucher][]
+- fix(cpp) Fix highlighting of unterminated raw strings (#2261) [David Benjamin][]
+- fix(javascript) `=>` function with nested `()` in params now works (#2502) [Josh Goebel][]
+- fix(typescript) `=>` function with nested `()` in params now works (#2502) [Josh Goebel][]
+- fix(yaml) Fix tags to include non-word characters (#2486) [Peter Plantinga][]
+- fix(swift) `@objcMembers` was being partially highlighted (#2543) [Nick Randall][]
+- enh(dart) Add `late` and `required` keywords, the `Never` built-in type, and nullable built-in types (#2550) [Sam Rawlins][]
+- enh(erlang) Add underscore separators to numeric literals (#2554) [Sergey Prokhorov][]
+- enh(handlebars) Support for sub-expressions, path-expressions, hashes, block-parameters and literals (#2344) [Nils Knappmeier][]
+- enh(protobuf) Support multiline comments (#2597) [Pavel Evstigneev][]
+- fix(toml) Improve key parsing (#2595) [Antoine du Hamel][]
+
+[Josh Goebel]: https://github.com/joshgoebel
+[Peter Plantinga]: https://github.com/pplantinga
+[David Benjamin]: https://github.com/davidben
+[Vania Kucher]: https://github.com/qWici
+[Hankun Lin]: https://github.com/Linhk1606
+[Nick Randall]: https://github.com/nicked
+[Sam Rawlins]: https://github.com/srawlins
+[Sergey Prokhorov]: https://github.com/seriyps
+[Nils Knappmeier]: https://github.com/nknapp
+[Martin (Lhoerion)]: https://github.com/Lhoerion
+[Jim Mason]: https://github.com/RocketMan
+[lioshi]: https://github.com/lioshi
+[Pavel Evstigneev]: https://github.com/Paxa
+[Antoine du Hamel]: https://github.com/aduh95
+
+
+## Version 10.0.2
+
+Brower build:
+
+- [Issue](https://github.com/highlightjs/highlight.js/issues/2505) (bug) Fix: Version 10 fails to load as CommonJS module. (#2511) [Josh Goebel][]
+- [Issue](https://github.com/highlightjs/highlight.js/issues/2505) (removal) AMD module loading support has been removed. (#2511) [Josh Goebel][]
+
+Parser Engine Changes:
+
+- [Issue](https://github.com/highlightjs/highlight.js/issues/2522) fix(parser) Fix freez issue with illegal 0 width matches (#2524) [Josh Goebel][]
+
+
+[Josh Goebel]: https://github.com/joshgoebel
+
+
+## Version 10.0.1
+
+Parser Engine Changes:
+
+- (bug) Fix sublanguage with no relevance score (#2506) [Josh Goebel][]
+
+[Josh Goebel]: https://github.com/joshgoebel
+
+
+## Version 10.0.0
 
 New languages:
 
-- none.
+- add(php-template) Explicit language to detect PHP templates (vs xml) [Josh Goebel][]
+- enh(python) Added `python-repl` for Python REPL sessions
+- add(never) Added 3rd party Never language support
 
 New themes:
 
-- none.
+- *Srcery* by [Chen Bin][]
 
-Core Changes:
+Parser Engine Changes:
 
-- improve regular expression detect (less false-positives) (#2380) [Josh Goebel][]
-- make `noHighlightRe` and `languagePrefixRe` configurable (#2374) [Josh Goebel][]
+- (bug) Fix `beginKeywords` to ignore . matches (#2434) [Josh Goebel][]
+- (enh) add `before:highlight` plugin API callback (#2395) [Josh Goebel][]
+- (enh) add `after:highlight` plugin API callback (#2395) [Josh Goebel][]
+- (enh) split out parse tree generation and HTML rendering concerns (#2404) [Josh Goebel][]
+- (enh) every language can have a `name` attribute now (#2400) [Josh Goebel][]
+- (enh) improve regular expression detect (less false-positives) (#2380) [Josh Goebel][]
+- (enh) make `noHighlightRe` and `languagePrefixRe` configurable (#2374) [Josh Goebel][]
 
 Language Improvements:
 
+- enh(python) Exclude parens from functions params (#2490) [Álvaro Mondéjar][]
+- enh(swift) Add `compactMap` to keywords as built_in (#2478) [Omid Golparvar][]
+- enh(nim) adds `func` keyword (#2468) [Adnan Yaqoob][]
+- enh(xml) deprecate ActionScript inside script tags (#2444) [Josh Goebel][]
+- fix(javascript) prevent get/set variables conflicting with keywords (#2440) [Josh Goebel][]
+- bug(clojure) Now highlights `defn-` properly (#2438) [Josh Goebel][]
+- enh(bash) default value is another variable (#2439) [Josh Goebel][]
+- enh(bash) string nested within string (#2439) [Josh Goebel][]
+- enh(bash) Add arithmetic expression support (#2439) [Josh Goebel][]
+- enh(clojure) Add support for global definitions name (#2347) [Alexandre Grison][]
+- enh(fortran) Support Fortran 77 style comments (#2416) [Josh Goebel][]
+- (csharp) add support for `@identifier` style identifiers (#2414) [Josh Goebel][]
+- fix(elixir) Support function names with a slash (#2406) [Josh Goebel][]
+- fix(javascript) comma is allowed in a "value container" (#2403) [Josh Goebel][]
 - enh(apache) add `deny` and `allow` keywords [Josh Goebel][]
 - enh(apache) highlight numeric attributes values [Josh Goebel][]
 - enh(apache) highlight IP addresses, ports, and strings in sections [Josh Goebel][]
@@ -34,16 +1005,24 @@ Language Improvements:
 - (fortran) Add Fortran 2018 keywords and coarray intrinsics (#2361) [Sam Miller][]
 - (delphi) highlight hexadecimal, octal, and binary numbers (#2370) [Robert Riebisch]()
 - enh(plaintext) added `text` and `txt` as alias (#2360) [Taufik Nurrohman][]
+- enh(powershell) added PowerShell v5.1/v7 default aliases as "built_in"s (#2423) [Sean Williams][]
+- enh(yaml) added support for timestamps (#2475) [Peter Plantinga][]
 
 Developer Tools:
 
 - added Dockerfile for optionally developing with a container
 
-[Josh Goebel]: https://github.com/yyyc514
+[Omid Golparvar]: https://github.com/omidgolparvar
+[Alexandre Grison]: https://github.com/agrison
+[Josh Goebel]: https://github.com/joshgoebel
+[Chen Bin]: https://github.com/redguardtoo
 [Sam Miller]: https://github.com/smillerc
 [Robert Riebisch]: https://github.com/bttrx
 [Taufik Nurrohman]: https://github.com/taufik-nurrohman
-[Josh Goebel]: https://github.com/yyyc514
+[Josh Goebel]: https://github.com/joshgoebel
+[Sean Williams]: https://github.com/hmmwhatsthisdo
+[Adnan Yaqoob]: https://github.com/adnanyaqoobvirk
+[Álvaro Mondéjar]: https://github.com/mondeja
 
 
 ## Version 9.18.1
@@ -52,7 +1031,7 @@ Grammar Improvements:
 
 - bug(coffeescript) fix freezing bug due to badly behaved regex (#2376) [Josh Goebel][]
 
-[Josh Goebel]: https://github.com/yyyc514
+[Josh Goebel]: https://github.com/joshgoebel
 
 
 ## Version 9.18.0
@@ -87,7 +1066,7 @@ Developer Tools:
 - feat(developer): add button to show parsed structure (#2345) [Nils Knappmeier][]
 
 [Jeffrey Arnold]: https://github.com/jrnold
-[Josh Goebel]: https://github.com/yyyc514
+[Josh Goebel]: https://github.com/joshgoebel
 [Philipp Engel]: https://github.com/interkosmos
 [Youssef Victor]: https://github.com/Youssef1313
 [Nils Knappmeier]: https://github.com/nknapp
@@ -99,7 +1078,7 @@ Fixes:
 
 - fix(parser): resolve IE 11 issue with Object.freeze() (#2319) [Josh Goebel][]
 
-[Josh Goebel]: https://github.com/yyyc514
+[Josh Goebel]: https://github.com/joshgoebel
 
 
 ## Version 9.17.0
@@ -149,7 +1128,7 @@ Language Improvements:
 - fix(objectivec): Handle multibyte character literals (#2268) [David Benjamin][]
 - enh(cpp): Add additional keywords (#2289) [Adrian Ostrowski][]
 
-[Josh Goebel]: https://github.com/yyyc514
+[Josh Goebel]: https://github.com/joshgoebel
 [Liam Nobel]: https://github.com/liamnobel
 [Carl Baxter]: https://github.com/cdbax
 [Milutin Kristofic]: https://github.com/milutin
@@ -161,6 +1140,7 @@ Language Improvements:
 [Mike Schall]: https://github.com/schallm
 [Kirill Saksin]: https://github.com/saksmt
 [Samia Ali]:https://github.com/samiaab1990
+[Erik Demaine]:https://github.com/edemaine
 
 
 ## Version 9.16.2
@@ -1058,7 +2038,7 @@ Notable fixes and improvements to existing languages:
 - HTML `
-
+
 ```
 
 This will find and highlight code inside of `
` tags; it tries
 to detect the language automatically. If automatic detection doesn’t
-work for you, you can specify the language in the `class` attribute:
+work for you, or you simply prefer to be explicit, you can specify the language manually in the using the `class` attribute:
 
-```html
-
...
-``` - -Classes may also be prefixed with either `language-` or `lang-`. ```html
...
``` -### Plaintext and Disabling Highlighting +#### Plaintext Code Blocks -To style arbitrary text like code, but without any highlighting, use the -`plaintext` class: +To apply the Highlight.js styling to plaintext without actually highlighting it, use the `plaintext` language: ```html -
...
+
...
``` -To disable highlighting of a tag completely, use the `nohighlight` class: +#### Ignoring a Code Block + +To skip highlighting of a code block completely, use the `nohighlight` class: ```html
...
``` -### Supported Languages - -The table below shows the full list of supported languages (and corresponding classes) that are bundled with the library. Note: Which languages are available may depend on how you've built or included the library in your app. See [Getting the Library](#getting-the-library) below. - -
-Reveal the full list of languages... - -| Language | Classes | Package | -| :-----------------------| :--------------------- | :------ | -| 1C | 1c | | -| 4D | 4d |[highlightjs-4d](https://github.com/highlightjs/highlightjs-4d) | -| ABNF | abnf | | -| Access logs | accesslog | | -| Ada | ada | | -| ARM assembler | armasm, arm | | -| AVR assembler | avrasm | | -| ActionScript | actionscript, as | | -| Alan | alan, i | [highlightjs-alan](https://github.com/highlightjs/highlightjs-alan) | -| AngelScript | angelscript, asc | | -| Apache | apache, apacheconf | | -| AppleScript | applescript, osascript | | -| Arcade | arcade | | -| AsciiDoc | asciidoc, adoc | | -| AspectJ | aspectj | | -| AutoHotkey | autohotkey | | -| AutoIt | autoit | | -| Awk | awk, mawk, nawk, gawk | | -| Axapta | axapta | | -| Bash | bash, sh, zsh | | -| Basic | basic | | -| BNF | bnf | | -| Brainfuck | brainfuck, bf | | -| C# | csharp, cs | | -| C | h | | -| C++ | cpp, hpp, cc, hh, c++, h++, cxx, hxx | | -| C/AL | cal | | -| Cache Object Script | cos, cls | | -| CMake | cmake, cmake.in | | -| Coq | coq | | -| CSP | csp | | -| CSS | css | | -| Cap’n Proto | capnproto, capnp | | -| Clojure | clojure, clj | | -| CoffeeScript | coffeescript, coffee, cson, iced | | -| Crmsh | crmsh, crm, pcmk | | -| Crystal | crystal, cr | | -| Cypher (Neo4j) | cypher | [highlightjs-cypher](https://github.com/highlightjs/highlightjs-cypher) | -| D | d | | -| DNS Zone file | dns, zone, bind | | -| DOS | dos, bat, cmd | | -| Dart | dart | | -| Delphi | delphi, dpr, dfm, pas, pascal, freepascal, lazarus, lpr, lfm | | -| Diff | diff, patch | | -| Django | django, jinja | | -| Dockerfile | dockerfile, docker | | -| dsconfig | dsconfig | | -| DTS (Device Tree) | dts | | -| Dust | dust, dst | | -| Dylan | dylan | [highlight-dylan](https://github.com/highlightjs/highlight-dylan) | -| EBNF | ebnf | | -| Elixir | elixir | | -| Elm | elm | | -| Erlang | erlang, erl | | -| Excel | excel, xls, xlsx | | -| Extempore | extempore, xtlang, xtm | [highlightjs-xtlang](https://github.com/highlightjs/highlightjs-xtlang) | -| F# | fsharp, fs | | -| FIX | fix | | -| Fortran | fortran, f90, f95 | | -| G-Code | gcode, nc | | -| Gams | gams, gms | | -| GAUSS | gauss, gss | | -| GDScript | godot, gdscript | [highlightjs-gdscript](https://github.com/highlightjs/highlightjs-gdscript) | -| Gherkin | gherkin | | -| GN for Ninja | gn, gni | [highlightjs-GN](https://github.com/highlightjs/highlightjs-GN/blob/master/gn.js) | -| Go | go, golang | | -| Grammatical Framework | gf | [highlightjs-gf](https://github.com/johnjcamilleri/highlightjs-gf) | -| Golo | golo, gololang | | -| Gradle | gradle | | -| Groovy | groovy | | -| HTML, XML | xml, html, xhtml, rss, atom, xjb, xsd, xsl, plist, svg | | -| HTTP | http, https | | -| Haml | haml | | -| Handlebars | handlebars, hbs, html.hbs, html.handlebars | | -| Haskell | haskell, hs | | -| Haxe | haxe, hx | | -| Hy | hy, hylang | | -| Ini, TOML | ini, toml | | -| Inform7 | inform7, i7 | | -| IRPF90 | irpf90 | | -| JSON | json | | -| Java | java, jsp | | -| JavaScript | javascript, js, jsx | | -| Kotlin | kotlin, kt | | -| LaTeX | tex | | -| Leaf | leaf | | -| Lasso | lasso, ls, lassoscript | | -| Less | less | | -| LDIF | ldif | | -| Lisp | lisp | | -| LiveCode Server | livecodeserver | | -| LiveScript | livescript, ls | | -| Lua | lua | | -| Makefile | makefile, mk, mak | | -| Markdown | markdown, md, mkdown, mkd | | -| Mathematica | mathematica, mma, wl | | -| Matlab | matlab | | -| Maxima | maxima | | -| Maya Embedded Language | mel | | -| Mercury | mercury | | -| mIRC Scripting Language | mirc, mrc | [highlightjs-mirc](https://github.com/highlightjs/highlightjs-mirc) | -| Mizar | mizar | | -| Mojolicious | mojolicious | | -| Monkey | monkey | | -| Moonscript | moonscript, moon | | -| N1QL | n1ql | | -| NSIS | nsis | | -| Nginx | nginx, nginxconf | | -| Nim | nimrod | | -| Nix | nix | | -| OCaml | ocaml, ml | | -| Objective C | objectivec, mm, objc, obj-c | | -| OpenGL Shading Language | glsl | | -| OpenSCAD | openscad, scad | | -| Oracle Rules Language | ruleslanguage | | -| Oxygene | oxygene | | -| PF | pf, pf.conf | | -| PHP | php, php3, php4, php5, php6, php7 | | -| Parser3 | parser3 | | -| Perl | perl, pl, pm | | -| Plaintext | plaintext, txt, text | | -| Pony | pony | | -| PostgreSQL & PL/pgSQL | pgsql, postgres, postgresql | | -| PowerShell | powershell, ps, ps1 | | -| Processing | processing | | -| Prolog | prolog | | -| Properties | properties | | -| Protocol Buffers | protobuf | | -| Puppet | puppet, pp | | -| Python | python, py, gyp | | -| Python profiler results | profile | | -| Q | k, kdb | | -| QML | qml | | -| R | r | | -| Razor CSHTML | cshtml, razor, razor-cshtml | [highlightjs-cshtml-razor](https://github.com/highlightjs/highlightjs-cshtml-razor) | -| ReasonML | reasonml, re | | -| RenderMan RIB | rib | | -| RenderMan RSL | rsl | | -| Roboconf | graph, instances | | -| Robot Framework | robot, rf | [highlightjs-robot](https://github.com/highlightjs/highlightjs-robot) | -| RPM spec files | rpm-specfile, rpm, spec, rpm-spec, specfile | [highlightjs-rpm-specfile](https://github.com/highlightjs/highlightjs-rpm-specfile) | -| Ruby | ruby, rb, gemspec, podspec, thor, irb | | -| Rust | rust, rs | | -| SAS | SAS, sas | | -| SCSS | scss | | -| SQL | sql | | -| STEP Part 21 | p21, step, stp | | -| Scala | scala | | -| Scheme | scheme | | -| Scilab | scilab, sci | | -| Shape Expressions | shexc | [highlightjs-shexc](https://github.com/highlightjs/highlightjs-shexc) | -| Shell | shell, console | | -| Smali | smali | | -| Smalltalk | smalltalk, st | | -| Solidity | solidity, sol | [highlightjs-solidity](https://github.com/highlightjs/highlightjs-solidity) | -| Stan | stan, stanfuncs | | -| Stata | stata | | -| Structured Text | iecst, scl, stl, structured-text | [highlightjs-structured-text](https://github.com/highlightjs/highlightjs-structured-text) | -| Stylus | stylus, styl | | -| SubUnit | subunit | | -| Supercollider | supercollider, sc | [highlightjs-supercollider](https://github.com/highlightjs/highlightjs-supercollider) | -| Swift | swift | | -| Tcl | tcl, tk | | -| Terraform (HCL) | terraform, tf, hcl | [highlightjs-terraform](https://github.com/highlightjs/highlightjs-terraform) | -| Test Anything Protocol | tap | | -| Thrift | thrift | | -| TP | tp | | -| Twig | twig, craftcms | | -| TypeScript | typescript, ts | | -| VB.Net | vbnet, vb | | -| VBScript | vbscript, vbs | | -| VHDL | vhdl | | -| Vala | vala | | -| Verilog | verilog, v | | -| Vim Script | vim | | -| x86 Assembly | x86asm | | -| XL | xl, tao | | -| XQuery | xquery, xpath, xq | | -| YAML | yml, yaml | | -| Zephir | zephir, zep | | - -Languages with the specified package name are defined in separate repositories -and not included in `highlight.min.js`. -
- - -## Custom Initialization - -When you need a bit more control over the initialization of -highlight.js, you can use the [`highlightBlock`][3] and [`configure`][4] -functions. This allows you to control *what* to highlight and *when*. - -Here’s an equivalent way to calling [`initHighlightingOnLoad`][1] using -vanilla JS: +### Node.js on the Server + +The bare minimum to auto-detect the language and highlight some code. + +```js +// load the library and ALL languages +hljs = require('highlight.js'); +html = hljs.highlightAuto('

Hello World!

').value +``` + +To load only a "common" subset of popular languages: + +```js +hljs = require('highlight.js/lib/common'); +``` + +To highlight code with a specific language, use `highlight`: + +```js +html = hljs.highlight('

Hello World!

', {language: 'xml'}).value +``` + +See [Importing the Library](#importing-the-library) for more examples of `require` vs `import` usage, etc. For more information about the result object returned by `highlight` or `highlightAuto` refer to the [api docs](https://highlightjs.readthedocs.io/en/latest/api.html). + + + +## Supported Languages + +Highlight.js supports over 180 languages in the core library. There are also 3rd party +language definitions available to support even more languages. You can find the full list of supported languages in [SUPPORTED_LANGUAGES.md][9]. + +## Custom Usage + +If you need a bit more control over the initialization of +Highlight.js, you can use the [`highlightElement`][3] and [`configure`][4] +functions. This allows you to better control *what* to highlight and *when*. + +For example, here’s the rough equivalent of calling [`highlightAll`][1] but doing the work manually instead: ```js document.addEventListener('DOMContentLoaded', (event) => { - document.querySelectorAll('pre code').forEach((block) => { - hljs.highlightBlock(block); + document.querySelectorAll('pre code').forEach((el) => { + hljs.highlightElement(el); }); }); ``` -You can use any tags instead of `
` to mark up your code. If
-you don't use a container that preserves line breaks you will need to
-configure highlight.js to use the `
` tag: +Please refer to the documentation for [`configure`][4] options. -```js -hljs.configure({useBR: true}); -document.querySelectorAll('div.code').forEach((block) => { - hljs.highlightBlock(block); +### Using custom HTML + +We strongly recommend `
` wrapping for code blocks. It's quite
+semantic and "just works" out of the box with zero fiddling. It is possible to
+use other HTML elements (or combos), but you may need to pay special attention to
+preserving linebreaks.
+
+Let's say your markup for code blocks uses divs:
+
+```html
+
...
+``` + +To highlight such blocks manually: + +```js +// first, find all the div.code blocks +document.querySelectorAll('div.code').forEach(el => { + // then highlight each + hljs.highlightElement(el); }); ``` -For other options refer to the documentation for [`configure`][4]. +Without using a tag that preserves linebreaks (like `pre`) you'll need some +additional CSS to help preserve them. You could also [pre and post-process line +breaks with a plug-in][brPlugin], but *we recommend using CSS*. + +[brPlugin]: https://github.com/highlightjs/highlight.js/issues/2559 + +To preserve linebreaks inside a `div` using CSS: + +```css +div.code { + white-space: pre; +} +``` + + +### Using with Vue.js + +See [highlightjs/vue-plugin](https://github.com/highlightjs/vue-plugin) for a simple Vue plugin that works great with Highlight.js. +An example of `vue-plugin` in action: -## Web Workers +```html +
+ + + + +
+``` + +### Using Web Workers You can run highlighting inside a web worker to avoid freezing the browser window while dealing with very big chunks of code. @@ -301,29 +238,64 @@ onmessage = (event) => { }; ``` -## Node.js +## Importing the Library + +First, you'll likely be installing the library via `npm` or `yarn` -- see [Getting the Library](#getting-the-library). + -You can use highlight.js with node to highlight content before sending it to the browser. -Make sure to use the `.value` property to get the formatted html. -For more info about the returned object refer to the api docs https://highlightjs.readthedocs.io/en/latest/api.html +### Node.js / `require` +Requiring the top-level library will load all languages: ```js -// require the highlight.js library including all languages +// require the highlight.js library, including all languages const hljs = require('./highlight.js'); const highlightedCode = hljs.highlightAuto('Hello World!').value ``` +For a smaller footprint, load our common subset of languages (the same set used for our default web build). + +```js +const hljs = require('highlight.js/lib/common'); +``` + +For the smallest footprint, load only the languages you need: + +```js +const hljs = require('highlight.js/lib/core'); +hljs.registerLanguage('xml', require('highlight.js/lib/languages/xml')); + +const highlightedCode = hljs.highlight('Hello World!', {language: 'xml'}).value +``` + + +### ES6 Modules / `import` + +*Note: You can also import directly from fully static URLs, such as our very own pre-built +ES6 Module CDN resources. See [Fetch via CDN](#fetch-via-cdn) for specific examples.* + +The default import will register all languages: + +```js +import hljs from 'highlight.js'; +``` + +It is more efficient to import only the library and register the languages you need: + +```js +import hljs from 'highlight.js/lib/core'; +import javascript from 'highlight.js/lib/languages/javascript'; +hljs.registerLanguage('javascript', javascript); +``` + +If your build tool processes CSS imports, you can also import the theme directly as a module: + ```js -// require the highlight.js library without languages -const hljs = require("highlight.js/lib/core"); -// separately require languages -hljs.registerLanguage('html', require('highlight.js/lib/languages/html')); -hljs.registerLanguage('sql', require('highlight.js/lib/languages/sql')); -// highlight with providing the language -const highlightedCode = hljs.highlight('html', 'Hello World!').value +import hljs from 'highlight.js'; +import 'highlight.js/styles/github.css'; ``` + ## Getting the Library You can get highlight.js as a hosted, or custom-build, browser script or @@ -332,22 +304,12 @@ both AMD and CommonJS, so if you wish you can use RequireJS or Browserify without having to build from source. The server module also works perfectly fine with Browserify, but there is the option to use a build specific to browsers rather than something meant for a server. -Head over to the [download page][5] for all the options. -**Don't link to GitHub directly.** The library is not supposed to work straight + +**Do not link to GitHub directly.** The library is not supposed to work straight from the source, it requires building. If none of the pre-packaged options work for you refer to the [building documentation][6]. -**The CDN-hosted package doesn't have all the languages.** Otherwise it'd be -too big. If you don't see the language you need in the ["Common" section][5], -it can be added manually: - -```html - -``` - **On Almond.** You need to use the optimizer to give the module a name. For example: @@ -355,55 +317,155 @@ example: r.js -o name=hljs paths.hljs=/path/to/highlight out=highlight.js ``` +### Fetch via CDN -### CommonJS +A prebuilt version of Highlight.js bundled with many common languages is hosted by several popular CDNs. +When using Highlight.js via CDN you can use Subresource Integrity for additional security. For details +see [DIGESTS.md](https://github.com/highlightjs/cdn-release/blob/main/DIGESTS.md). -You can import Highlight.js as a CommonJS-module: +#### cdnjs ([link](https://cdnjs.com/libraries/highlight.js)) -```bash -npm install highlight.js --save +##### Common JS + +```html + + + + ``` -In your application: +##### ES6 Modules -```js -import hljs from 'highlight.js'; +````html + + + +```` + + +#### jsdelivr ([link](https://www.jsdelivr.com/package/gh/highlightjs/cdn-release)) + +##### Common JS + +```html + + + + ``` -The default import imports all languages! Therefore it is likely to be more efficient to import only the library and the languages you need: +##### ES6 Modules -```js -import hljs from 'highlight.js/lib/core'; -import javascript from 'highlight.js/lib/languages/javascript'; -hljs.registerLanguage('javascript', javascript); +```html + + ``` -To set the syntax highlighting style, if your build tool processes CSS from your JavaScript entry point, you can import the stylesheet directly into your CommonJS-module: +#### unpkg ([link](https://unpkg.com/browse/@highlightjs/cdn-assets/)) -```js -import hljs from 'highlight.js/lib/core'; -import 'highlight.js/styles/github.css'; +##### Common JS + +```html + + + + +``` + +##### ES6 Modules + +```html + + +``` + + +**Note:** *The CDN-hosted `highlight.min.js` package doesn't bundle every language.* It would be +very large. You can find our list of "common" languages that we bundle by default on our [download page][5]. + +#### Download prebuilt CDN assets + +You can also download and self-host the same assets we serve up via our own CDNs. We publish those builds to the [cdn-release](https://github.com/highlightjs/cdn-release) GitHub repository. You can easily pull individual files off the CDN endpoints with `curl`, etc; if say you only needed `highlight.min.js` and a single CSS file. + +There is also an npm package [@highlightjs/cdn-assets](https://www.npmjs.com/package/@highlightjs/cdn-assets) if pulling the assets in via `npm` or `yarn` would be easier for your build process. + +### Download from our website + +The [download page][5] can quickly generate a custom single-file minified bundle including only the languages you desire. + +**Note:** [Building from source](#build-from-source) can produce slightly smaller builds than the website download. + + +### Install via NPM package + +Our NPM package including all supported languages can be installed with NPM or Yarn: + +```bash +npm install highlight.js +# or +yarn add highlight.js +``` + +Alternatively, you can build the NPM package from source. + + +### Build from Source + +The [current source code][10] is always available on GitHub. + +```bash +node tools/build.js -t node +node tools/build.js -t browser :common +node tools/build.js -t cdn :common ``` +See our [building documentation][6] for more information. + + +## Requirements + +Highlight.js works on all modern browsers and currently supported Node.js versions. You'll need the following software to contribute to the core library: + +- Node.js >= 12.x +- npm >= 6.x + ## License -Highlight.js is released under the BSD License. See [LICENSE][7] file +Highlight.js is released under the BSD License. See our [LICENSE][7] file for details. + ## Links -The official site for the library is at . +The official website for the library is . Further in-depth documentation for the API and other topics is at . -Authors and contributors are listed in the [AUTHORS.txt][8] file. +A list of the Core Team and contributors can be found in the [CONTRIBUTORS.md][8] file. -[1]: http://highlightjs.readthedocs.io/en/latest/api.html#inithighlightingonload +[1]: http://highlightjs.readthedocs.io/en/latest/api.html#highlightall [2]: http://highlightjs.readthedocs.io/en/latest/css-classes-reference.html -[3]: http://highlightjs.readthedocs.io/en/latest/api.html#highlightblock-block -[4]: http://highlightjs.readthedocs.io/en/latest/api.html#configure-options +[3]: http://highlightjs.readthedocs.io/en/latest/api.html#highlightelement +[4]: http://highlightjs.readthedocs.io/en/latest/api.html#configure [5]: https://highlightjs.org/download/ [6]: http://highlightjs.readthedocs.io/en/latest/building-testing.html -[7]: https://github.com/highlightjs/highlight.js/blob/master/LICENSE -[8]: https://github.com/highlightjs/highlight.js/blob/master/AUTHORS.txt +[7]: https://github.com/highlightjs/highlight.js/blob/main/LICENSE +[8]: https://github.com/highlightjs/highlight.js/blob/main/CONTRIBUTORS.md +[9]: https://github.com/highlightjs/highlight.js/blob/main/SUPPORTED_LANGUAGES.md +[10]: https://github.com/highlightjs/ diff --git a/README.ru.md b/README.ru.md deleted file mode 100644 index 198ee969cf..0000000000 --- a/README.ru.md +++ /dev/null @@ -1,142 +0,0 @@ -# Highlight.js - -Highlight.js — это инструмент для подсветки синтаксиса, написанный на JavaScript. Он работает -и в браузере, и на сервере. Он работает с практически любой HTML разметкой, не -зависит от каких-либо фреймворков и умеет автоматически определять язык. - - -## Начало работы - -Минимум, что нужно сделать для использования highlight.js на веб-странице — это -подключить библиотеку, CSS-стили и вызывать [`initHighlightingOnLoad`][1]: - -```html - - - -``` - -Библиотека найдёт и раскрасит код внутри тегов `
`, попытавшись
-автоматически определить язык. Когда автоопределение не срабатывает, можно явно
-указать язык в атрибуте class:
-
-```html
-
...
-``` - -Список поддерживаемых классов языков доступен в [справочнике по классам][2]. -Класс также можно предварить префиксами `language-` или `lang-`. - -Чтобы отключить подсветку для какого-то блока, используйте класс `nohighlight`: - -```html -
...
-``` - -## Инициализация вручную - -Чтобы иметь чуть больше контроля за инициализацией подсветки, вы можете -использовать функции [`highlightBlock`][3] и [`configure`][4]. Таким образом -можно управлять тем, *что* и *когда* подсвечивать. - -Вот пример инициализации, эквивалентной вызову [`initHighlightingOnLoad`][1], но -с использованием `document.addEventListener`: - -```js -document.addEventListener('DOMContentLoaded', (event) => { - document.querySelectorAll('pre code').forEach((block) => { - hljs.highlightBlock(block); - }); -}); -``` - -Вы можете использовать любые теги разметки вместо `
`. Если
-используете контейнер, не сохраняющий переводы строк, вам нужно сказать
-highlight.js использовать для них тег `
`: - -```js -hljs.configure({useBR: true}); - -document.querySelectorAll('div.code').forEach((block) => { - hljs.highlightBlock(block); -}); -``` - -Другие опции можно найти в документации функции [`configure`][4]. - - -## Web Workers - -Подсветку можно запустить внутри web worker'а, чтобы окно -браузера не подтормаживало при работе с большими кусками кода. - -В основном скрипте: - -```js -addEventListener('load', () => { - const code = document.querySelector('#code'); - const worker = new Worker('worker.js'); - worker.onmessage = (event) => { code.innerHTML = event.data; } - worker.postMessage(code.textContent); -}); -``` - -В worker.js: - -```js -onmessage = (event) => { - importScripts('/highlight.pack.js'); - const result = self.hljs.highlightAuto(event.data); - postMessage(result.value); -}; -``` - - -## Установка библиотеки - -Highlight.js можно использовать в браузере прямо с CDN хостинга или скачать -индивидуальную сборку, а также установив модуль на сервере. На -[странице загрузки][5] подробно описаны все варианты. - -**Не подключайте GitHub напрямую.** Библиотека не предназначена для -использования в виде исходного кода, а требует отдельной сборки. Если вам не -подходит ни один из готовых вариантов, читайте [документацию по сборке][6]. - -**Файл на CDN содержит не все языки.** Иначе он будет слишком большого размера. -Если нужного вам языка нет в [категории "Common"][5], можно дообавить его -вручную: - -```html - -``` - -**Про Almond.** Нужно задать имя модуля в оптимизаторе, например: - -``` -r.js -o name=hljs paths.hljs=/path/to/highlight out=highlight.js -``` - - -## Лицензия - -Highlight.js распространяется под лицензией BSD. Подробнее читайте файл -[LICENSE][7]. - - -## Ссылки - -Официальный сайт билиотеки расположен по адресу . - -Более подробная документация по API и другим темам расположена на -. - -Авторы и контрибьюторы перечислены в файле [AUTHORS.ru.txt][8] file. - -[1]: http://highlightjs.readthedocs.io/en/latest/api.html#inithighlightingonload -[2]: http://highlightjs.readthedocs.io/en/latest/css-classes-reference.html -[3]: http://highlightjs.readthedocs.io/en/latest/api.html#highlightblock-block -[4]: http://highlightjs.readthedocs.io/en/latest/api.html#configure-options -[5]: https://highlightjs.org/download/ -[6]: http://highlightjs.readthedocs.io/en/latest/building-testing.html -[7]: https://github.com/highlightjs/highlight.js/blob/master/LICENSE -[8]: https://github.com/highlightjs/highlight.js/blob/master/AUTHORS.ru.txt diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000000..086a03424a --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,19 @@ +# Security Policy + +Due to both time and resource constrains the Highlight.js core team fully supports only the current major/minor release of the library. Prior major releases may be supported for a short time after new major releases are issued. Problems with minor releases are often resolved by upgrading to the most recent minor release. + +### Release Status + +| Version | Support | Status | +| :-----: | :-: | :------ | +| 11.x | :white_check_mark: | The 11.x series recieves regular updates, new features & security fixes. | +| 10.7.x | :x: | No longer supported.
See [VERSION_11_UPGRADE.md](https://github.com/highlightjs/highlight.js/blob/master/VERSION_11_UPGRADE.md).| +| <= 10.4.0 | :x: | Known vulnerabities. | +| <= 9.18.5 | :x: | Known vulnerabities. [EOL](https://github.com/highlightjs/highlight.js/issues/2877) | +| 7.x, 8.x | :x: | Obsolete. Known vulnerabities. | + + +### Reporting a Vulnerability + +Many vulnerabilities can simply be reported (and tracked) via our [GitHub issues](https://github.com/highlightjs/highlight.js/issues). If you feel your issue is more sensitive than that you can always reach us via email: [security@highlightjs.org](mailto:security@highlightjs.org) + diff --git a/SUPPORTED_LANGUAGES.md b/SUPPORTED_LANGUAGES.md new file mode 100644 index 0000000000..c73a294af5 --- /dev/null +++ b/SUPPORTED_LANGUAGES.md @@ -0,0 +1,241 @@ +# Supported Languages + +The table below shows the full list of languages (and corresponding classes/aliases) supported by Highlight.js. Languages that list a **Package** below are 3rd party languages and are not bundled with the core library. You can find their repositories by following the links. + +**Note:** The languages available will depend on how you've built or are included the library in your app. For example our default minified web build includes only ~40 popular languages. See [Getting the Library][1] and [Importing the Library][2] in the README for examples of how to load additional/specific languages. + + +| Language | Aliases | Package | +| :-----------------------| :--------------------- | :------ | +| 1C | 1c | | +| 4D | 4d |[highlightjs-4d](https://github.com/highlightjs/highlightjs-4d) | +| ABAP | sap-abap, abap |[highlight-sap-abap](https://github.com/highlightjs/highlightjs-sap-abap) | +| ABNF | abnf | | +| Access logs | accesslog | | +| Ada | ada | | +| Arduino (C++ w/Arduino libs) | arduino, ino | | +| ARM assembler | armasm, arm | | +| AVR assembler | avrasm | | +| ActionScript | actionscript, as | | +| Alan IF | alan, i | [highlightjs-alan](https://github.com/highlightjs/highlightjs-alan) | +| Alan | ln | [highlightjs-alan](https://github.com/alantech/highlightjs-alan) | +| AngelScript | angelscript, asc | | +| Apache | apache, apacheconf | | +| AppleScript | applescript, osascript | | +| Arcade | arcade | | +| AsciiDoc | asciidoc, adoc | | +| AspectJ | aspectj | | +| AutoHotkey | autohotkey | | +| AutoIt | autoit | | +| Awk | awk, mawk, nawk, gawk | | +| Bash | bash, sh, zsh | | +| Basic | basic | | +| BBCode | bbcode | [highlightjs-bbcode](https://github.com/RedGuy12/highlightjs-bbcode) | +| Blade (Laravel) | blade | [highlightjs-blade](https://github.com/miken32/highlightjs-blade) | +| BNF | bnf | | +| Brainfuck | brainfuck, bf | | +| C# | csharp, cs | | +| C | c, h | | +| C++ | cpp, hpp, cc, hh, c++, h++, cxx, hxx | | +| C/AL | cal | | +| Cache Object Script | cos, cls | | +| CMake | cmake, cmake.in | | +| Coq | coq | | +| CSP | csp | | +| CSS | css | | +| Cap’n Proto | capnproto, capnp | | +| Chaos | chaos, kaos | [highlightjs-chaos](https://github.com/chaos-lang/highlightjs-chaos) | +| Chapel | chapel, chpl | [highlightjs-chapel](https://github.com/chapel-lang/highlightjs-chapel) | +| Cisco CLI | cisco | [highlightjs-cisco-cli](https://github.com/BMatheas/highlightjs-cisco-cli) | +| Clojure | clojure, clj | | +| CoffeeScript | coffeescript, coffee, cson, iced | | +| CpcdosC+ | cpc | [highlightjs-cpcdos](https://github.com/SPinti-Software/highlightjs-cpcdos) | +| Crmsh | crmsh, crm, pcmk | | +| Crystal | crystal, cr | | +| cURL | curl | [highlightjs-curl](https://github.com/highlightjs/highlightjs-curl) | +| Cypher (Neo4j) | cypher | [highlightjs-cypher](https://github.com/highlightjs/highlightjs-cypher) | +| D | d | | +| Dafny | dafny | [highlightjs-dafny](https://github.com/ConsenSys/highlightjs-dafny)| +| Dart | dart | | +| Delphi | dpr, dfm, pas, pascal | | +| Diff | diff, patch | | +| Django | django, jinja | | +| DNS Zone file | dns, zone, bind | | +| Dockerfile | dockerfile, docker | | +| DOS | dos, bat, cmd | | +| dsconfig | dsconfig | | +| DTS (Device Tree) | dts | | +| Dust | dust, dst | | +| Dylan | dylan | [highlightjs-dylan](https://github.com/highlightjs/highlightjs-dylan) | +| EBNF | ebnf | | +| Elixir | elixir | | +| Elm | elm | | +| Erlang | erlang, erl | | +| Excel | excel, xls, xlsx | | +| Extempore | extempore, xtlang, xtm | [highlightjs-xtlang](https://github.com/highlightjs/highlightjs-xtlang) | +| F# | fsharp, fs | | +| FIX | fix | | +| Fortran | fortran, f90, f95 | | +| G-Code | gcode, nc | | +| Gams | gams, gms | | +| GAUSS | gauss, gss | | +| GDScript | godot, gdscript | [highlightjs-gdscript](https://github.com/highlightjs/highlightjs-gdscript) | +| Gherkin | gherkin | | +| Glimmer and EmberJS | hbs, glimmer, html.hbs, html.handlebars, htmlbars | [highlightjs-glimmer](https://github.com/NullVoxPopuli/highlightjs-glimmer) | +| GN for Ninja | gn, gni | [highlightjs-GN](https://github.com/highlightjs/highlightjs-GN) | +| Go | go, golang | | +| Grammatical Framework | gf | [highlightjs-gf](https://github.com/johnjcamilleri/highlightjs-gf) | +| Golo | golo, gololang | | +| Gradle | gradle | | +| Groovy | groovy | | +| GSQL | gsql | [highlightjs-gsql](https://github.com/DanBarkus/highlightjs-gsql) | +| HTML, XML | xml, html, xhtml, rss, atom, xjb, xsd, xsl, plist, svg | | +| HTTP | http, https | | +| Haml | haml | | +| Handlebars | handlebars, hbs, html.hbs, html.handlebars | | +| Haskell | haskell, hs | | +| Haxe | haxe, hx | | +| High-level shader language| hlsl | [highlightjs-hlsl](https://github.com/highlightjs/highlightjs-hlsl) | +| Hy | hy, hylang | | +| Ini, TOML | ini, toml | | +| Inform7 | inform7, i7 | | +| IRPF90 | irpf90 | | +| JSON | json | | +| Java | java, jsp | | +| JavaScript | javascript, js, jsx | | +| Jolie | jolie, iol, ol | [highlightjs-jolie](https://github.com/xiroV/highlightjs-jolie) | +| Julia | julia, julia-repl | | +| Kotlin | kotlin, kt | | +| LaTeX | tex | | +| Leaf | leaf | | +| Lean | lean | [highlightjs-lean](https://github.com/leanprover-community/highlightjs-lean) | +| Lasso | lasso, ls, lassoscript | | +| Less | less | | +| LDIF | ldif | | +| Lisp | lisp | | +| LiveCode Server | livecodeserver | | +| LiveScript | livescript, ls | | +| Lua | lua | | +| Makefile | makefile, mk, mak, make | | +| Markdown | markdown, md, mkdown, mkd | | +| Mathematica | mathematica, mma, wl | | +| Matlab | matlab | | +| Maxima | maxima | | +| Maya Embedded Language | mel | | +| Mercury | mercury | | +| mIRC Scripting Language | mirc, mrc | [highlightjs-mirc](https://github.com/highlightjs/highlightjs-mirc) | +| Mizar | mizar | | +| MKB | mkb | [highlightjs-mkb](https://github.com/Dereavy/highlightjs-mkb) | +| Mojolicious | mojolicious | | +| Monkey | monkey | | +| Moonscript | moonscript, moon | | +| N1QL | n1ql | | +| NSIS | nsis | | +| Never | never | [highlightjs-never](https://github.com/never-lang/highlightjs-never) | +| Nginx | nginx, nginxconf | | +| Nim | nim, nimrod | | +| Nix | nix | | +| Object Constraint Language | ocl | [highlightjs-ocl](https://github.com/nhomble/highlightjs-ocl) | +| OCaml | ocaml, ml | | +| Objective C | objectivec, mm, objc, obj-c, obj-c++, objective-c++ | | +| OpenGL Shading Language | glsl | | +| OpenSCAD | openscad, scad | | +| Oracle Rules Language | ruleslanguage | | +| Oxygene | oxygene | | +| PF | pf, pf.conf | | +| PHP | php | | +| Papyrus | papyrus, psc |[highlightjs-papyrus](https://github.com/Pickysaurus/highlightjs-papyrus) | +| Parser3 | parser3 | | +| Perl | perl, pl, pm | | +| Pine Script | pine, pinescript | [highlightjs-pine](https://github.com/jeyllani/highlightjs-pine) | +| Plaintext | plaintext, txt, text | | +| Pony | pony | | +| PostgreSQL & PL/pgSQL | pgsql, postgres, postgresql | | +| PowerShell | powershell, ps, ps1 | | +| Processing | processing | | +| Prolog | prolog | | +| Properties | properties | | +| Protocol Buffers | protobuf | | +| Puppet | puppet, pp | | +| Python | python, py, gyp | | +| Python profiler results | profile | | +| Python REPL | python-repl, pycon | | +| Q# | qsharp | [highlightjs-qsharp](https://github.com/fedonman/highlightjs-qsharp) | +| Q | k, kdb | | +| QML | qml | | +| R | r | | +| Razor CSHTML | cshtml, razor, razor-cshtml | [highlightjs-cshtml-razor](https://github.com/highlightjs/highlightjs-cshtml-razor) | +| ReasonML | reasonml, re | | +| Rebol & Red | redbol, rebol, red, red-system | [highlightjs-redbol](https://github.com/oldes/highlightjs-redbol) | +| RenderMan RIB | rib | | +| RenderMan RSL | rsl | | +| RiScript | risc, riscript | [highlightjs-riscript](https://github.com/highlightjs/highlightjs-riscript) | +| Roboconf | graph, instances | | +| Robot Framework | robot, rf | [highlightjs-robot](https://github.com/highlightjs/highlightjs-robot) | +| RPM spec files | rpm-specfile, rpm, spec, rpm-spec, specfile | [highlightjs-rpm-specfile](https://github.com/highlightjs/highlightjs-rpm-specfile) | +| Ruby | ruby, rb, gemspec, podspec, thor, irb | | +| Rust | rust, rs | | +| SAS | SAS, sas | | +| SCSS | scss | | +| SQL | sql | | +| STEP Part 21 | p21, step, stp | | +| Scala | scala | | +| Scheme | scheme | | +| Scilab | scilab, sci | | +| Shape Expressions | shexc | [highlightjs-shexc](https://github.com/highlightjs/highlightjs-shexc) | +| Shell | shell, console | | +| Smali | smali | | +| Smalltalk | smalltalk, st | | +| SML | sml, ml | | +| Solidity | solidity, sol | [highlightjs-solidity](https://github.com/highlightjs/highlightjs-solidity) | +| Splunk SPL | spl | [highlightjs-spl](https://github.com/swsoyee/highlightjs-spl) | +| Stan | stan, stanfuncs | | +| Stata | stata | | +| Structured Text | iecst, scl, stl, structured-text | [highlightjs-structured-text](https://github.com/highlightjs/highlightjs-structured-text) | +| Stylus | stylus, styl | | +| SubUnit | subunit | | +| Supercollider | supercollider, sc | [highlightjs-supercollider](https://github.com/highlightjs/highlightjs-supercollider) | +| Svelte | svelte | [highlightjs-svelte](https://github.com/AlexxNB/highlightjs-svelte) | +| Swift | swift | | +| Tcl | tcl, tk | | +| Terraform (HCL) | terraform, tf, hcl | [highlightjs-terraform](https://github.com/highlightjs/highlightjs-terraform) | +| Test Anything Protocol | tap | | +| Thrift | thrift | | +| TP | tp | | +| Transact-SQL | tsql | [highlightjs-tsql](https://github.com/highlightjs/highlightjs-tsql) | +| Twig | twig, craftcms | | +| TypeScript | typescript, ts | | +| Unicorn Rails log | unicorn-rails-log | [highlightjs-unicorn-rails-log](https://github.com/sweetppro/highlightjs-unicorn-rails-log) | +| VB.Net | vbnet, vb | | +| VBA | vba | [highlightjs-vba](https://github.com/dullin/highlightjs-vba) | +| VBScript | vbscript, vbs | | +| VHDL | vhdl | | +| Vala | vala | | +| Verilog | verilog, v | | +| Vim Script | vim | | +| X# | xsharp, xs, prg | [highlightjs-xsharp](https://github.com/InfomindsAg/highlightjs-xsharp) | +| X++ | axapta, x++ | | +| x86 Assembly | x86asm | | +| XL | xl, tao | | +| XQuery | xquery, xpath, xq | | +| YAML | yml, yaml | | +| ZenScript | zenscript, zs |[highlightjs-zenscript](https://github.com/highlightjs/highlightjs-zenscript) | +| Zephir | zephir, zep | | + + + +## Alias Overlap + +If you are using either of these languages at the same time please be sure to +use the full name and not the alias to avoid any ambiguity. + +| Language | Overlap | +| :-----------------------| :--------------------- | +| SML | ml | +| OCaml | ml | +| Lasso | ls | +| LiveScript | ls | + + +[1]: https://github.com/highlightjs/highlight.js#getting-the-library +[2]: https://github.com/highlightjs/highlight.js#importing-the-library diff --git a/VERSION_10_BREAKING.md b/VERSION_10_BREAKING_CHANGES.md similarity index 81% rename from VERSION_10_BREAKING.md rename to VERSION_10_BREAKING_CHANGES.md index 1a6473e86d..cc9ac1cce5 100644 --- a/VERSION_10_BREAKING.md +++ b/VERSION_10_BREAKING_CHANGES.md @@ -1,11 +1,17 @@ ## Version 10 Breaking Changes Incompatibilities: + - chore(parser): compressed version 9.x language definitions no longer supported (rebuild them minified) [Josh Goebel][] - `nohightlight` and `no-highlight` are the only "ignore me" css classes now (`plain` and `text` no longer count) (to get the old behavior you can customize `noHighlightRe`) +- a grammar with a top-level `self` reference will now always throw an error + (previously in safe mode this would be silently ignored) +- PHP templates are now detected as `php-template`, not `xml` Renamed Language Files: + +- chore(parser): `htmlbars.js` now depends on `handlebars.js` [Josh Goebel][] - chore(parser): rename `nimrod.js` to `nim.js` [Josh Goebel][] - chore(parser): rename `cs.js` to `csharp.js` [Josh Goebel][] - chore(parser): rename `tex.js` to `latex.js` [Josh Goebel][] @@ -17,14 +23,16 @@ Renamed Language Files: (https://github.com/highlightjs/highlight.js/issues/2146) Legacy Browser Issues: + - **We're now using ES2015 features in the codebase. Internet Explorer 11 is no longer supported.** - In general legacy browsers are no longer supported. - chore(parser): remove `load` listener in favor of only the newer `DOMContentLoaded` [Josh Goebel][] Removed styles: + - chore(styles): darkula.css (use darcula.css instead) [Josh Goebel][] -[Josh Goebel]: https://github.com/yyyc514 +[Josh Goebel]: https://github.com/joshgoebel --- diff --git a/VERSION_10_UPGRADE.md b/VERSION_10_UPGRADE.md new file mode 100644 index 0000000000..645c88e7da --- /dev/null +++ b/VERSION_10_UPGRADE.md @@ -0,0 +1,58 @@ +# Upgrading to Version 10.0 + +Welcome to version 10.0. This a major release and therefore will contain breaking changes. + +## Breaking Changes + +Our normal minor releases try to never break anything, holding all breaking changes for major releases. +We tried to squeeze in as many as we could this time so that after 10.0 ships we'll be back to quiet sailing for a while before we need to push version 11. That said, we're very conservative about what we consider a breaking change. + +*IE, if there it could possibly break things for anyone, it's typically a breaking change.* The fact is a vast majority of users should upgrade and probably not notice any changes at all. + +See [VERSION_10_BREAKING_CHANGES.md](https://github.com/highlightjs/highlight.js/blob/main/VERSION_10_BREAKING_CHANGES.md) for a comprehensive list of breaking changes, but here is a summary... if you use: + +### Core highlight.js lib on the client (with no extra CDN languages) + +Just keep doing that. + +- If you're using `darkula.css`, you'll need to change that to `darcula.css` +- The minified distributable has changed from `.pack.js` to `.min.js`, update your name when you update your URL. +- If your users have very old browsers, they may no longer be supported (no more IE11, etc.). (We're using ES2015 code now.) +- `nohighlight` or `no-highlight` are the only two CSS classes that will SKIP highlighting completely. `*text*` and `*plain*` no longer will do this. + +### Core highlight.js lib on the client (plus additional CDN languages) + +Quite a few grammars have been renamed. Ex: `nimrod.js` is now `nim.js`. + +- Check the renamed grammars to see if you might need to update your links. +- Be aware that you can't use version 9 CDN JS files anymore, they aren't compatible. +- Plus read the above list of items. + +### highlight.js on the server (via NPM) and only use the public API + +If you're just pulling in the FULL library (`require('./highlight.js')`) just keep doing that. You might not need to change anything. + +- If you're manually loading a smaller set of languages and using `registerLanguage` make sure you check out all the renamed grammars and dependency changes. +- Read the client-side lists above also. + +### highlight.js on the server (via NPM) with a custom integration + +Read the complete breaking changes list carefully. + +- Read the client-side lists above also. + +### highlight.js lib on the client, with source directly from our GitHub repo + +That will no longer work. The source needs to be built to work properly and cannot be used "raw" unless you've also setup your own build pipeline (rollup, etc.). Fetch a static build from the CDN, the [cdn-release repo](https://github.com/highlightjs/cdn-release) or use the new [`highlightjs-dist`]() NPM package. + +### highlight.js source code directly from our GitHub repo with a custom integration + +All bets are off, since we only try to guarantee stability of our NPM and CDN builds and the public API. Read all the breaking changes and perhaps skim the commit history. + +- We're using ES6 modules now. +- We're using an entirely new build system. +- The source will likely become more and more modular during the 10.0 timeline. + +## Enjoy and good luck. + +As always if you have any questions or issues, jump on the [Github Issues](https://github.com/highlightjs/highlight.js/issues). diff --git a/VERSION_11_UPGRADE.md b/VERSION_11_UPGRADE.md new file mode 100644 index 0000000000..29364ca14e --- /dev/null +++ b/VERSION_11_UPGRADE.md @@ -0,0 +1,203 @@ +# Upgrading to Highlight.js v11.0 + +- [Overview of Breaking Changes](#overview-of-breaking-changes) + - [Built-in set of "Common" Languages](#built-in-set-of-common-languages) + - [Language Files](#language-files) + - [Language Aliases](#language-aliases) + - [Styles and CSS](#styles-and-css) + - [Grammar Scopes](#grammar-scopes) + - [Behavioral changes](#behavioral-changes) + - [API changes](#api-changes) + - [Changes to Result Data](#changes-to-result-data) + - [Feature Removal](#feature-removal) + - [Small Things](#small-things) + - [Upgrading from Version 9.x](#upgrading-from-version-9x) + + +## Overview of Breaking Changes + +Welcome to version 11.0. This a major release and therefore contains breaking changes. Below is a complete list of those such changes. + + +### Built-in set of "Common" Languages + +The default `highlight.min.js` build **removes** a few less popular grammars: + +- apache +- http +- nginx +- properties +- coffeescript + +If you need any of these, you can always create a custom build. + +Ref: https://github.com/highlightjs/highlight.js/issues/2848 + + +### Language Files + +This would matter if you are requiring any of these files directly (via Node.js or CDN). + +- `htmlbars` has been removed. Use `handlebars` instead. +- `c-like` has been removed. Use `c`, `cpp`, or `arduino`. +- `sql_more` has been removed. Use `sql` instead or a more specific 3rd party grammar. + + +### Language Aliases + +This would matter if you are using these aliases. + +- `php3`,`php4`,`php5`, `php6`, `php7`, and `php8` have been removed. Use `php` instead. +- `zsh` has been removed. Use `sh` or `bash` instead. +- `freepascal`, `lazarus`, `lpr`, and `lpm` removed. Use `delphi` instead. + +You can of course re-register any of these aliases easily if necessary. For example to restore the PHP aliases: + +```js +hljs.registerAliases(["php3","php4","php5","php6","php7","php8"],{ languageName: "php" }) +``` + +### Styles and CSS + +- The default padding on `.hljs` element has been increased and is now `1em` (it was `0.5em` previously). If your design depends on the smaller spacing you may need to update your CSS to override. +- `schoolbook` no longer has a custom lined background, it is solid color now. The old image and CSS can be found in the [10-stable branch](https://github.com/highlightjs/highlight.js/tree/10-stable/src/styles) if you wish to manually copy it into your project. +- `github` includes significant changes to more properly match modern GitHub syntax highlighting. If you desire the old theme you can manually copy it into your project from the [10-stable branch](https://github.com/highlightjs/highlight.js/tree/10-stable/src/styles). +- `github-gist` has been removed in favor of `github` as GitHub and GitHub Gist have converged. If you desire the theme you can manually copy it into your project from the [10-stable branch](https://github.com/highlightjs/highlight.js/tree/10-stable/src/styles). +- The `.hljs` CSS selector is now further scoped. It now targets `code.hljs` (inline code) and `pre code.hljs` (code blocks). If you are using a different element you may need to update your CSS to reapply some styling. +- All [Base16 themes](https://github.com/highlightjs/base16-highlightjs) now live in the `styles/base16` directory - this means some CSS files have moved. Please confirm themes you use reference the new locations. + + +#### Grammar Scopes + +- `.meta-string` removed/deprecated. Use `.meta .string` (a nested scope) instead. See [meta-keyword][]. +- `.meta-keyword` removed/deprecated. Use `.meta .keyword` (a nested scope) instead. See [meta-keyword][]. + +### Behavioral changes + +- `after:highlightElement` plugin callback is now fired *after* the DOM has been updated, not before. + +#### API changes + +- The option `ignoreIllegals` is now `true` by default (for `highlight()`). Previously it was `false`. +- The `highlight(language,code, ...args)` API no longer accepts `continuation` as a 4th argument. +- The `highlight(language,code, ...args)` API is deprecated (to be removed in 12.0). + +The new call signature is `highlight(code, {options})`. ([see docs](https://highlightjs.readthedocs.io/en/latest/api.html#highlight)) + +Code using the old API: + +```js +// highlight(language, code, ignoreIllegals, continuation) +highlight("javascript", "var a = 5;", true) +``` +...would be upgraded to the newer API as follows: + +```js +// highlight(code, {language, ignoreIllegals}) +highlight("var a = 5;", {language: "javascript", ignoreIllegals: true}) +``` + +The new API purposely does not support `continuation` as this is only intended for internal library usage. + +- `initHighlighting()` is deprecated (to be removed in 12.0). +- `initHighlightingOnLoad()` is deprecated (to be removed in 12.0). + +**Use `highlightAll()` instead.** ([see docs](https://highlightjs.readthedocs.io/en/latest/api.html#highlight-all)) The old functions are now simply aliases of `highlightAll()`. The new function may be called before or after the DOM is loaded and should do the correct thing in all cases, replacing the need for the previous individual functions. + +Note: `highlightAll()` does not guard against calling itself repeatedly as the previous functions did. Your code should be careful to avoid doing this. + +- `highlightBlock()` is deprecated (to be removed in 12.0). + +**Use `highlightElement()` instead.** ([see docs](https://highlightjs.readthedocs.io/en/latest/api.html#highlight-element)) This is merely a naming change. + +Note: The object literal passed to the `before:highlightElement` callback now passes the element in the `el` key vs the `block` key. + +##### Changes to Result Data + +- `highlightAuto()`'s `second_best` key has been renamed to `secondBest` +- `highlightElement()`'s result now no longer includes a `re` key. Use the `relevance` key now. +- `highlight()` renames some result keys to more clearly mark them as private: `_top`, `_emitter`, and `_illegalBy`. You should not depend on these keys as they are subject to change at any time. +- The `relevance` key returned by `highlight()` is no longer guaranteed to be an even integer. +- `highlightElement` now always tags blocks with a consistent `language-[name]` class + +This behavior was inconsistent before. Sometimes `[name]` class would be added, sometimes the alias name would be added, something no class would be added. now `language-[name]` is always added. This also affects sublanguage `span` tags which now also include the `language-` prefix. + +#### Feature Removal + +- HTML auto-passthru is now no longer included in core. Use a plugin instead. For a possible plugin please see [#2889](https://github.com/highlightjs/highlight.js/issues/2889). + +An example: + +```html +

+var a = 4;
+var a = 4;
+
+``` + +Unescaped HTML like this will now be ignored (stripped before highlighting) and a warning will be logged to the console. All HTML to be highlighted should be properly escaped to avoid potential HTML/JS injection attacks. + +- `fixMarkup` has been removed. + +This function was deprecated in v10.2. It is not our goal to provide random string utilities. You may need to provide your own replacement [Ref: #2534](https://github.com/highlightjs/highlight.js/issues/2634) + +- `CSS_NUMBER_MODE` has been removed. + +This rule was too broad for bring inclusion in core and has been removed. + +- `lexemes` mode attribute has been removed. + +Use the new `keywords.$pattern` instead. + +Before: + +```js +{ + keywords: "do.it start.now begin.later end.immediately" + lexemes: /[a-z.]+/ +} +``` + +After: + +```js +{ + keywords: { + $pattern: /[a-z.]+/ + keyword: "do.it start.now begin.later end.immediately", + } +} +``` + +This may required converting your `keywords` key into an object if it's not already (as shown above). + +- `endSameAsBegin` mode attribute has been removed. + +Use the new `END_SAME_AS_BEGIN` mode rule/function instead. + +- `useBR` configuration has been removed. + +This configuration option was deprecated in v10.1. Use a plugin or preferably simply CSS `white-space: pre`. [Ref: #2559](https://github.com/highlightjs/highlight.js/issues/2559) + + +- `tabReplace` configuration has been removed. + +This configuration option was deprecated in v10.5. Use a plugin or pre-render content instead with desired spacing. [Ref: #2874](https://github.com/highlightjs/highlight.js/issues/2874) + + + + + +### Small Things + +- The `regex` utility `join` has been renamed to `_eitherRewriteBackreferences` (this has always been intended for internal use only) + +### Upgrading from Version 9.x + +If you're upgrading all the way from version 9 it may still be helpful to review all the breaking changes in version 10 as well: + +- [VERSION_10_UPGRADE.md](https://github.com/highlightjs/highlight.js/blob/main/VERSION_10_UPGRADE.md) +- [VERSION_10_BREAKING_CHANGES.md](https://github.com/highlightjs/highlight.js/blob/main/VERSION_10_BREAKING_CHANGES.md) + + +[meta-keyword]: https://github.com/highlightjs/highlight.js/pull/3167 diff --git a/demo/demo.js b/demo/demo.js index bb46e7a605..49f6bcef1f 100644 --- a/demo/demo.js +++ b/demo/demo.js @@ -1,121 +1,48 @@ -(function() { - 'use strict'; - - hljs.debugMode(); - - var $window = $(window), - $languages = $('#languages div'), - $linkTitle = $('link[title]'), - $categoryContainer = $('#categories'), - $styleContainer = $('#styles'); - - function resizeLists() { - var screenHeight = $window.height() - - $categoryContainer.css('max-height', screenHeight / 4); - $categoryContainer.perfectScrollbar('update'); - $styleContainer.height( - screenHeight - $styleContainer.position().top - 20 - ); - $styleContainer.perfectScrollbar('update'); - } - - function selectCategory(category) { - $languages.each(function(i, language) { - var $language = $(language); - - if ($language.hasClass(category)) { - var code = $language.find('code'); - - if (!code.hasClass('hljs')) { - hljs.highlightBlock(code.get(0)); - } - - $language.show(); - } else { - $language.hide(); - } - }); - - $(document).scrollTop(0); - } - - function categoryKey(c) { - return c === 'common' ? '' : c === 'misc' ? 'z' : c === 'all' ? 'zz' : c; - } - - function initCategories() { - var $categories, categoryNames; - var categories = {}; - - $languages.each(function(i, div) { - if (!div.className) { - div.className += 'misc'; - } - div.className += ' all'; - div.className.split(' ').forEach(function(c) { - categories[c] = (categories[c] || 0) + 1; - }); - }); - - categoryNames = Object.keys(categories); - - categoryNames.sort(function(a, b) { - a = categoryKey(a); - b = categoryKey(b); - return a < b ? -1 : a > b ? 1 : 0; - }); - - categoryNames.forEach(function(c) { - $categoryContainer.append( - '
  • ' + c + ' (' + categories[c] +')
  • ' - ); - }); - - $categories = $categoryContainer.find('li'); - - $categories.click(function() { - var $category = $(this); - - $categories.removeClass('current'); - $category.addClass('current'); - selectCategory($category.data('category')); - }); - - $categories.first().click(); - $categoryContainer.perfectScrollbar(); - } - - function selectStyle(style) { - $linkTitle.each(function(i, link) { - link.disabled = (link.title !== style); - }); - } - - function initStyles() { - var $styles; - - $linkTitle.each(function(i, link) { - $styleContainer.append('
  • ' + link.title + '
  • '); - }); - - $styles = $styleContainer.find('li'); - - $styles.click(function() { - var $style = $(this); - - $styles.removeClass('current'); - $style.addClass('current'); - selectStyle($style.text()); - }); - $styles.first().click(); - $styleContainer.perfectScrollbar(); - } - - $(function() { - initCategories(); - initStyles(); - $window.resize(resizeLists); - resizeLists(); +hljs.debugMode(); +hljs.highlightAll(); + +document.querySelectorAll(".categories li a").forEach((category) => { + category.addEventListener("click", (event) => { + event.preventDefault(); + + const current = document.querySelector(".categories .current"); + const currentCategory = current.dataset.category; + const nextCategory = event.target.dataset.category; + + if (currentCategory !== nextCategory) { + current.classList.remove("current"); + event.target.classList.add("current"); + + document + .querySelectorAll(`.${currentCategory}`) + .forEach((language) => language.classList.add("hidden")); + document + .querySelectorAll(`.${nextCategory}`) + .forEach((language) => language.classList.remove("hidden")); + + window.scrollTo(0, 0); + } + }); +}); + +document.querySelectorAll(".styles li a").forEach((style) => { + style.addEventListener("click", (event) => { + event.preventDefault(); + + const current = document.querySelector(".styles .current"); + const currentStyle = current.textContent; + const nextStyle = event.target.textContent; + + if (currentStyle !== nextStyle) { + document + .querySelector(`link[title="${nextStyle}"]`) + .removeAttribute("disabled"); + document + .querySelector(`link[title="${currentStyle}"]`) + .setAttribute("disabled", "disabled"); + + current.classList.remove("current"); + event.target.classList.add("current"); + } }); -}).call(this); +}); diff --git a/demo/index.html b/demo/index.html index 6ff356a32c..ad6371748c 100644 --- a/demo/index.html +++ b/demo/index.html @@ -4,52 +4,44 @@ highlight.js demo + - - <% _.each(styles, function(style) { %> - + <% styles.forEach(({ name, path }) => {%> + <% }); %> + + + - - -
    - -
    - <% _.each(languages, function(language) { %> - <% var categories = language.categories; %> -
    0) { %>class="<%= categories.join(' ') %>"<% } %>> -

    <%- language.prettyName %>

    -
    <%- language.sample %>
    + + +
    + <% languages.forEach(({categories, name, prettyName, sample}) => { %> +
    +

    <%= prettyName %>

    +
    <%- sample %>
    <% }); %> -
    - -
    - - - - - + diff --git a/demo/perfect-scrollbar.min.css b/demo/perfect-scrollbar.min.css deleted file mode 100644 index fc452aff82..0000000000 --- a/demo/perfect-scrollbar.min.css +++ /dev/null @@ -1,5 +0,0 @@ -/*! perfect-scrollbar - v0.5.7 -* http://noraesae.github.com/perfect-scrollbar/ -* Copyright (c) 2014 Hyunje Alex Jun; Licensed MIT */ - -.ps-container.ps-active-x>.ps-scrollbar-x-rail,.ps-container.ps-active-y>.ps-scrollbar-y-rail{display:block}.ps-container>.ps-scrollbar-x-rail{display:none;position:absolute;-webkit-border-radius:4px;-moz-border-radius:4px;-ms-border-radius:4px;border-radius:4px;opacity:0;-ms-filter:"alpha(Opacity=0)";filter:alpha(opacity=0);-webkit-transition:background-color .2s linear,opacity .2s linear;-moz-transition:background-color .2s linear,opacity .2s linear;-o-transition:background-color .2s linear,opacity .2s linear;transition:background-color .2s linear,opacity .2s linear;bottom:3px;height:8px}.ps-container>.ps-scrollbar-x-rail>.ps-scrollbar-x{position:absolute;background-color:#aaa;-webkit-border-radius:4px;-moz-border-radius:4px;-ms-border-radius:4px;border-radius:4px;-webkit-transition:background-color .2s linear;-moz-transition:background-color .2s linear;-o-transition:background-color .2s linear;transition:background-color .2s linear;bottom:0;height:8px}.ps-container>.ps-scrollbar-x-rail.in-scrolling{background-color:#eee;opacity:.9;-ms-filter:"alpha(Opacity=90)";filter:alpha(opacity=90)}.ps-container>.ps-scrollbar-y-rail{display:none;position:absolute;-webkit-border-radius:4px;-moz-border-radius:4px;-ms-border-radius:4px;border-radius:4px;opacity:0;-ms-filter:"alpha(Opacity=0)";filter:alpha(opacity=0);-webkit-transition:background-color .2s linear,opacity .2s linear;-moz-transition:background-color .2s linear,opacity .2s linear;-o-transition:background-color .2s linear,opacity .2s linear;transition:background-color .2s linear,opacity .2s linear;right:3px;width:8px}.ps-container>.ps-scrollbar-y-rail>.ps-scrollbar-y{position:absolute;background-color:#aaa;-webkit-border-radius:4px;-moz-border-radius:4px;-ms-border-radius:4px;border-radius:4px;-webkit-transition:background-color .2s linear;-moz-transition:background-color .2s linear;-o-transition:background-color .2s linear;transition:background-color .2s linear;right:0;width:8px}.ps-container>.ps-scrollbar-y-rail.in-scrolling{background-color:#eee;opacity:.9;-ms-filter:"alpha(Opacity=90)";filter:alpha(opacity=90)}.ps-container:hover>.ps-scrollbar-x-rail,.ps-container:hover>.ps-scrollbar-y-rail{opacity:.6;-ms-filter:"alpha(Opacity=60)";filter:alpha(opacity=60)}.ps-container:hover>.ps-scrollbar-x-rail.in-scrolling,.ps-container:hover>.ps-scrollbar-y-rail.in-scrolling{background-color:#eee;opacity:.9;-ms-filter:"alpha(Opacity=90)";filter:alpha(opacity=90)}.ps-container:hover>.ps-scrollbar-x-rail:hover{background-color:#eee;opacity:.9;-ms-filter:"alpha(Opacity=90)";filter:alpha(opacity=90)}.ps-container:hover>.ps-scrollbar-x-rail:hover>.ps-scrollbar-x{background-color:#999}.ps-container:hover>.ps-scrollbar-y-rail:hover{background-color:#eee;opacity:.9;-ms-filter:"alpha(Opacity=90)";filter:alpha(opacity=90)}.ps-container:hover>.ps-scrollbar-y-rail:hover>.ps-scrollbar-y{background-color:#999} \ No newline at end of file diff --git a/demo/perfect-scrollbar.min.js b/demo/perfect-scrollbar.min.js deleted file mode 100644 index ff3f5ac9c8..0000000000 --- a/demo/perfect-scrollbar.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! perfect-scrollbar - v0.5.7 -* http://noraesae.github.com/perfect-scrollbar/ -* Copyright (c) 2014 Hyunje Alex Jun; Licensed MIT */ -(function(e){"use strict";"function"==typeof define&&define.amd?define(["jquery"],e):"object"==typeof exports?e(require("jquery")):e(jQuery)})(function(e){"use strict";function t(e){return"string"==typeof e?parseInt(e,10):~~e}var o={wheelSpeed:1,wheelPropagation:!1,minScrollbarLength:null,maxScrollbarLength:null,useBothWheelAxes:!1,useKeyboard:!0,suppressScrollX:!1,suppressScrollY:!1,scrollXMarginOffset:0,scrollYMarginOffset:0,includePadding:!1},n=0,r=function(){var e=n++;return function(t){var o=".perfect-scrollbar-"+e;return t===void 0?o:t+o}};e.fn.perfectScrollbar=function(n,l){return this.each(function(){function i(e,o){var n=e+o,r=E-W;I=0>n?0:n>r?r:n;var l=t(I*(D-E)/(E-W));S.scrollTop(l)}function a(e,o){var n=e+o,r=x-Y;X=0>n?0:n>r?r:n;var l=t(X*(M-x)/(x-Y));S.scrollLeft(l)}function c(e){return L.minScrollbarLength&&(e=Math.max(e,L.minScrollbarLength)),L.maxScrollbarLength&&(e=Math.min(e,L.maxScrollbarLength)),e}function s(){var e={width:x};e.left=O?S.scrollLeft()+x-M:S.scrollLeft(),B?e.bottom=q-S.scrollTop():e.top=H+S.scrollTop(),A.css(e);var t={top:S.scrollTop(),height:E};z?t.right=O?M-S.scrollLeft()-Q-N.outerWidth():Q-S.scrollLeft():t.left=O?S.scrollLeft()+2*x-M-F-N.outerWidth():F+S.scrollLeft(),_.css(t),K.css({left:X,width:Y-U}),N.css({top:I,height:W-G})}function d(){S.removeClass("ps-active-x"),S.removeClass("ps-active-y"),x=L.includePadding?S.innerWidth():S.width(),E=L.includePadding?S.innerHeight():S.height(),M=S.prop("scrollWidth"),D=S.prop("scrollHeight"),!L.suppressScrollX&&M>x+L.scrollXMarginOffset?(C=!0,Y=c(t(x*x/M)),X=t(S.scrollLeft()*(x-Y)/(M-x))):(C=!1,Y=0,X=0,S.scrollLeft(0)),!L.suppressScrollY&&D>E+L.scrollYMarginOffset?(k=!0,W=c(t(E*E/D)),I=t(S.scrollTop()*(E-W)/(D-E))):(k=!1,W=0,I=0,S.scrollTop(0)),X>=x-Y&&(X=x-Y),I>=E-W&&(I=E-W),s(),C&&S.addClass("ps-active-x"),k&&S.addClass("ps-active-y")}function u(){var t,o,n=!1;K.bind(j("mousedown"),function(e){o=e.pageX,t=K.position().left,A.addClass("in-scrolling"),n=!0,e.stopPropagation(),e.preventDefault()}),e(R).bind(j("mousemove"),function(e){n&&(a(t,e.pageX-o),d(),e.stopPropagation(),e.preventDefault())}),e(R).bind(j("mouseup"),function(){n&&(n=!1,A.removeClass("in-scrolling"))}),t=o=null}function p(){var t,o,n=!1;N.bind(j("mousedown"),function(e){o=e.pageY,t=N.position().top,n=!0,_.addClass("in-scrolling"),e.stopPropagation(),e.preventDefault()}),e(R).bind(j("mousemove"),function(e){n&&(i(t,e.pageY-o),d(),e.stopPropagation(),e.preventDefault())}),e(R).bind(j("mouseup"),function(){n&&(n=!1,_.removeClass("in-scrolling"))}),t=o=null}function f(e,t){var o=S.scrollTop();if(0===e){if(!k)return!1;if(0===o&&t>0||o>=D-E&&0>t)return!L.wheelPropagation}var n=S.scrollLeft();if(0===t){if(!C)return!1;if(0===n&&0>e||n>=M-x&&e>0)return!L.wheelPropagation}return!0}function v(){function e(e){var t=e.originalEvent.deltaX,o=-1*e.originalEvent.deltaY;return(t===void 0||o===void 0)&&(t=-1*e.originalEvent.wheelDeltaX/6,o=e.originalEvent.wheelDeltaY/6),e.originalEvent.deltaMode&&1===e.originalEvent.deltaMode&&(t*=10,o*=10),t!==t&&o!==o&&(t=0,o=e.originalEvent.wheelDelta),[t,o]}function t(t){var n=e(t),r=n[0],l=n[1];o=!1,L.useBothWheelAxes?k&&!C?(l?S.scrollTop(S.scrollTop()-l*L.wheelSpeed):S.scrollTop(S.scrollTop()+r*L.wheelSpeed),o=!0):C&&!k&&(r?S.scrollLeft(S.scrollLeft()+r*L.wheelSpeed):S.scrollLeft(S.scrollLeft()-l*L.wheelSpeed),o=!0):(S.scrollTop(S.scrollTop()-l*L.wheelSpeed),S.scrollLeft(S.scrollLeft()+r*L.wheelSpeed)),d(),o=o||f(r,l),o&&(t.stopPropagation(),t.preventDefault())}var o=!1;window.onwheel!==void 0?S.bind(j("wheel"),t):window.onmousewheel!==void 0&&S.bind(j("mousewheel"),t)}function g(){var t=!1;S.bind(j("mouseenter"),function(){t=!0}),S.bind(j("mouseleave"),function(){t=!1});var o=!1;e(R).bind(j("keydown"),function(n){if((!n.isDefaultPrevented||!n.isDefaultPrevented())&&t){for(var r=document.activeElement?document.activeElement:R.activeElement;r.shadowRoot;)r=r.shadowRoot.activeElement;if(!e(r).is(":input,[contenteditable]")){var l=0,i=0;switch(n.which){case 37:l=-30;break;case 38:i=30;break;case 39:l=30;break;case 40:i=-30;break;case 33:i=90;break;case 32:case 34:i=-90;break;case 35:i=n.ctrlKey?-D:-E;break;case 36:i=n.ctrlKey?S.scrollTop():E;break;default:return}S.scrollTop(S.scrollTop()-i),S.scrollLeft(S.scrollLeft()+l),o=f(l,i),o&&n.preventDefault()}}})}function b(){function e(e){e.stopPropagation()}N.bind(j("click"),e),_.bind(j("click"),function(e){var o=t(W/2),n=e.pageY-_.offset().top-o,r=E-W,l=n/r;0>l?l=0:l>1&&(l=1),S.scrollTop((D-E)*l)}),K.bind(j("click"),e),A.bind(j("click"),function(e){var o=t(Y/2),n=e.pageX-A.offset().left-o,r=x-Y,l=n/r;0>l?l=0:l>1&&(l=1),S.scrollLeft((M-x)*l)})}function h(){function t(){var e=window.getSelection?window.getSelection():document.getSlection?document.getSlection():{rangeCount:0};return 0===e.rangeCount?null:e.getRangeAt(0).commonAncestorContainer}function o(){r||(r=setInterval(function(){return P()?(S.scrollTop(S.scrollTop()+l.top),S.scrollLeft(S.scrollLeft()+l.left),d(),void 0):(clearInterval(r),void 0)},50))}function n(){r&&(clearInterval(r),r=null),A.removeClass("in-scrolling"),_.removeClass("in-scrolling")}var r=null,l={top:0,left:0},i=!1;e(R).bind(j("selectionchange"),function(){e.contains(S[0],t())?i=!0:(i=!1,n())}),e(window).bind(j("mouseup"),function(){i&&(i=!1,n())}),e(window).bind(j("mousemove"),function(e){if(i){var t={x:e.pageX,y:e.pageY},r=S.offset(),a={left:r.left,right:r.left+S.outerWidth(),top:r.top,bottom:r.top+S.outerHeight()};t.xa.right-3?(l.left=5,A.addClass("in-scrolling")):l.left=0,t.ya.top+3-t.y?-5:-20,_.addClass("in-scrolling")):t.y>a.bottom-3?(l.top=5>t.y-a.bottom+3?5:20,_.addClass("in-scrolling")):l.top=0,0===l.top&&0===l.left?n():o()}})}function w(t,o){function n(e,t){S.scrollTop(S.scrollTop()-t),S.scrollLeft(S.scrollLeft()-e),d()}function r(){b=!0}function l(){b=!1}function i(e){return e.originalEvent.targetTouches?e.originalEvent.targetTouches[0]:e.originalEvent}function a(e){var t=e.originalEvent;return t.targetTouches&&1===t.targetTouches.length?!0:t.pointerType&&"mouse"!==t.pointerType&&t.pointerType!==t.MSPOINTER_TYPE_MOUSE?!0:!1}function c(e){if(a(e)){h=!0;var t=i(e);p.pageX=t.pageX,p.pageY=t.pageY,f=(new Date).getTime(),null!==g&&clearInterval(g),e.stopPropagation()}}function s(e){if(!b&&h&&a(e)){var t=i(e),o={pageX:t.pageX,pageY:t.pageY},r=o.pageX-p.pageX,l=o.pageY-p.pageY;n(r,l),p=o;var c=(new Date).getTime(),s=c-f;s>0&&(v.x=r/s,v.y=l/s,f=c),e.stopPropagation(),e.preventDefault()}}function u(){!b&&h&&(h=!1,clearInterval(g),g=setInterval(function(){return P()?.01>Math.abs(v.x)&&.01>Math.abs(v.y)?(clearInterval(g),void 0):(n(30*v.x,30*v.y),v.x*=.8,v.y*=.8,void 0):(clearInterval(g),void 0)},10))}var p={},f=0,v={},g=null,b=!1,h=!1;t&&(e(window).bind(j("touchstart"),r),e(window).bind(j("touchend"),l),S.bind(j("touchstart"),c),S.bind(j("touchmove"),s),S.bind(j("touchend"),u)),o&&(window.PointerEvent?(e(window).bind(j("pointerdown"),r),e(window).bind(j("pointerup"),l),S.bind(j("pointerdown"),c),S.bind(j("pointermove"),s),S.bind(j("pointerup"),u)):window.MSPointerEvent&&(e(window).bind(j("MSPointerDown"),r),e(window).bind(j("MSPointerUp"),l),S.bind(j("MSPointerDown"),c),S.bind(j("MSPointerMove"),s),S.bind(j("MSPointerUp"),u)))}function m(){S.bind(j("scroll"),function(){d()})}function T(){S.unbind(j()),e(window).unbind(j()),e(R).unbind(j()),S.data("perfect-scrollbar",null),S.data("perfect-scrollbar-update",null),S.data("perfect-scrollbar-destroy",null),K.remove(),N.remove(),A.remove(),_.remove(),S=A=_=K=N=C=k=x=E=M=D=Y=X=q=B=H=W=I=Q=z=F=O=j=null}function y(){d(),m(),u(),p(),b(),h(),v(),(J||V)&&w(J,V),L.useKeyboard&&g(),S.data("perfect-scrollbar",S),S.data("perfect-scrollbar-update",d),S.data("perfect-scrollbar-destroy",T)}var L=e.extend(!0,{},o),S=e(this),P=function(){return!!S};if("object"==typeof n?e.extend(!0,L,n):l=n,"update"===l)return S.data("perfect-scrollbar-update")&&S.data("perfect-scrollbar-update")(),S;if("destroy"===l)return S.data("perfect-scrollbar-destroy")&&S.data("perfect-scrollbar-destroy")(),S;if(S.data("perfect-scrollbar"))return S.data("perfect-scrollbar");S.addClass("ps-container");var x,E,M,D,C,Y,X,k,W,I,O="rtl"===S.css("direction"),j=r(),R=this.ownerDocument||document,A=e("
    ").appendTo(S),K=e("
    ").appendTo(A),q=t(A.css("bottom")),B=q===q,H=B?null:t(A.css("top")),U=t(A.css("borderLeftWidth"))+t(A.css("borderRightWidth")),_=e("
    ").appendTo(S),N=e("
    ").appendTo(_),Q=t(_.css("right")),z=Q===Q,F=z?null:t(_.css("left")),G=t(_.css("borderTopWidth"))+t(_.css("borderBottomWidth")),J="ontouchstart"in window||window.DocumentTouch&&document instanceof window.DocumentTouch,V=null!==window.navigator.msMaxTouchPoints;return y(),S})}}); \ No newline at end of file diff --git a/demo/style.css b/demo/style.css index 6fa82319b8..e23f06d2e7 100644 --- a/demo/style.css +++ b/demo/style.css @@ -1,74 +1,136 @@ +@import url(https://fonts.googleapis.com/css?family=PT+Sans:400,700,400italic); + /* Base styles */ body { - color: #F0F0F0; + color: #f0f0f0; background: #500; - font-family: Arial, sans; - margin: 0; padding: 0; + font-family: PT Sans, DejaVu Sans, Arial, sans; + margin: 0; + padding: 0; } a { - color: white; + color: #f0f0f0; } h1 { - font: bold 150% Arial, sans-serif; - margin: 0; padding: 1em 0; + font-size: 150%; + margin: 0; + padding: 1em 0 0.5em; } h2 { - font: bold 120% Arial, sans-serif; + font-size: 120%; margin: 1.5em 0 0.5em 0; } pre { margin: 0; - font-size: medium; } -#sidebar { - float: left; width: 16em; height: 1000em; position: fixed; - padding: 1px 1em; +code { + display: block; + font-family: Consolas, Monaco, monospace; + font-size: 10pt; + overflow-x: auto; + padding: 0.5em; + scrollbar-width: thin; + -moz-tab-size: 4; + tab-size: 4; +} + +pre code.hljs { + /* compatible with school-book */ + padding: 16px 30px 20px; +} + +aside { background: #600; - box-shadow: 0 0 15px black; + bottom: 0; + box-shadow: 0 0 1em #000000; + display: flex; + flex-direction: column; + left: 0; + padding: 0 1.5em 1em; + position: fixed; + top: 0; + width: 16em; +} + +.categories { + flex: 0 4 25%; } -#sidebar ul, -#sidebar li { +.styles { + flex: 1 1 50%; +} + +aside ul, +aside li { list-style: none; - margin: 0; padding: 0; + margin: 0; + padding: 0; } -#sidebar ul { +aside ul { position: relative; - overflow-y: hidden; + overflow-y: auto; + scrollbar-width: thin; } -#sidebar li { - padding: 1px 1em; - cursor: pointer; +aside ul::-webkit-scrollbar { + width: 8px; } -#sidebar li:hover { - background: #500; +aside ul::-webkit-scrollbar-track { + background: #4d0607; + border-radius: 5px; +} + +aside ul::-webkit-scrollbar-thumb { + background: #926868; + border-right: 1px solid #4d0607; + border-left: 1px solid #4d0607; + border-radius: 5px; +} + +aside ul::-webkit-scrollbar-thumb:hover { + background: #7f5555; +} + +aside ul::-webkit-scrollbar-thumb:active { + background: #5d3333; +} + +aside ul a { + display: block; + padding: 1px 0.5em 1px 1em; + text-decoration: none; } -#sidebar li.current:before { - content: '▶'; font-size: smaller; - position: absolute; left: 0; +aside ul a:focus, +aside ul a:hover { + background: #500; + outline: none; } -#content { - margin-left: 19em; +aside ul a.current:before { + content: "▶"; + font-size: smaller; + position: absolute; + left: 0; } -#languages { +main { + margin-left: 18em; + min-width: 36em; padding: 1px 4em; } -#languages div { - display: none; +main > div { + margin: 2.5em 0 3em; } -#languages div { - margin: 3em 0; +.hidden { + display: none; } diff --git a/docs/api.rst b/docs/api.rst index 646b1d2f67..f4301a2889 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -1,25 +1,23 @@ -Library API -=========== +Core API +======== Highlight.js exports a few functions as methods of the ``hljs`` object. +.. _newerapi: -``highlight(languageName, code, ignore_illegals, continuation)`` ---------------------------------------------------------- +highlight +--------- -Core highlighting function. -Accepts a language name, or an alias, and a string with the code to highlight. -The ``ignore_illegals`` parameter, when present and evaluates to a true value, -forces highlighting to finish even in case of detecting illegal syntax for the -language instead of throwing an exception. -The ``continuation`` is an optional mode stack representing unfinished parsing. -When present, the function will restart parsing from this state instead of -initializing a new one. This is used internally for `sublanguage` support. +:: -Note: `continuation` is NOT intended to support line-by-line highlighting -because there is no requirement that a grammar handle linebreaks in any special -way. It's quite possible for a grammar to have a single mode/regex that matches -MANY lines at once. This is not discouraged and entirely up to the grammar. + highlight(code, {language, ignoreIllegals}) + +Core highlighting function. Accepts the code to highlight (string) and a list of options (object). +The ``language`` parameter must be present and specify the language name or alias +of the grammar to be used for highlighting. +The ``ignoreIllegals`` is an optional parameter than when true forces highlighting +to finish even in case of detecting illegal syntax for the +language instead of throwing an exception. Returns an object with the following properties: @@ -28,10 +26,16 @@ Returns an object with the following properties: * ``value``: HTML string with highlighting markup * ``top``: top of the current mode stack * ``illegal``: boolean representing whether any illegal matches were found +* ``code``: the original raw code + -``highlightAuto(code, languageSubset)`` ----------------------------------------- +highlightAuto +------------- + +:: + + highlightAuto(code, languageSubset) Highlighting with language detection. Accepts a string with the code to highlight and an optional array of language names and aliases restricting detection to only those languages. The subset can also be set with ``configure``, but the local parameter overrides the option if set. @@ -41,113 +45,196 @@ Returns an object with the following properties: * ``language``: detected language * ``relevance``: integer value representing the relevance score * ``value``: HTML string with highlighting markup -* ``second_best``: object with the same structure for second-best heuristically detected language (may be absent) - - -``fixMarkup(value)`` --------------------- +* ``secondBest``: object with the same structure for second-best heuristically detected language (may be absent) -Post-processing of the highlighted markup. Currently consists of replacing indentation TAB characters and using ``
    `` tags instead of new-line characters. Options are set globally with ``configure``. -Accepts a string with the highlighted markup. +highlightElement +---------------- +:: -``highlightBlock(block)`` -------------------------- + highlightElement(element) Applies highlighting to a DOM node containing code. This function is the one to use to apply highlighting dynamically after page load -or within initialization code of third-party Javascript frameworks. +or within initialization code of third-party JavaScript frameworks. The function uses language detection by default but you can specify the language -in the ``class`` attribute of the DOM node. See the :doc:`class reference -` for all available language names and aliases. +in the ``class`` attribute of the DOM node. See the :doc:`scopes reference +` for all available language names and scopes. + + + + +highlightAll +------------ +Applies highlighting to all elements on a page matching the configured ``cssSelector``. +The default ``cssSelector`` value is ``'pre code'``, which highlights all code blocks. +This can be called before or after the page's ``onload`` event has fired. + + +configure +--------- + +:: -``configure(options)`` ----------------------- + configure(options) Configures global options: -* ``tabReplace``: a string used to replace TAB characters in indentation. -* ``useBR``: a flag to generate ``
    `` tags instead of new-line characters in the output, useful when code is marked up using a non-``
    `` container.
     * ``classPrefix``: a string prefix added before class names in the generated markup, used for backwards compatibility with stylesheets.
     * ``languages``: an array of language names and aliases restricting auto detection to only these languages.
     * ``languageDetectRe``: a regex to configure how CSS class names map to language (allows class names like say `color-as-php` vs the default of `language-php`, etc.)
    -* ``noHighlightRe``: a regex to configure which CSS classes are to be skipped completely
    +* ``noHighlightRe``: a regex to configure which CSS classes are to be skipped completely.
    +* ``cssSelector``: a CSS selector to configure which elements are affected by ``hljs.highlightAll``. Defaults to ``'pre code'``.
    +* ``ignoreUnescapedHTML``: do not log warnings to console about unescaped HTML in code blocks
    +* ``throwUnescapedHTML``: throw a ``HTMLInjectionError`` when ``highlightElement`` is asked to highlight content that includes unescaped HTML
    +
     
     Accepts an object representing options with the values to updated. Other options don't change
     ::
     
       hljs.configure({
    -    tabReplace: '    ', // 4 spaces
    +    noHighlightRe: /^do-not-highlightme$/i,
    +    languageDetectRe: /\bgrammar-([\w-]+)\b/i, // for `grammar-swift` style CSS naming
         classPrefix: ''     // don't append class prefix
                             // … other options aren't changed
    -  })
    -  hljs.initHighlighting();
    +  });
     
     
    -``initHighlighting()``
    -----------------------
    +registerLanguage
    +----------------
     
    -Applies highlighting to all ``
    ..
    `` blocks on a page. +:: + registerLanguage(languageName, languageDefinition) +Adds new language to the library under the specified name. Used mostly internally. -``initHighlightingOnLoad()`` ----------------------------- +* ``languageName``: a string with the name of the language being registered +* ``languageDefinition``: a function that returns an object which represents the + language definition. The function is passed the ``hljs`` object to be able + to use common regular expressions defined within it. -Attaches highlighting to the page load event. +unregisterLanguage +------------------ -``registerLanguage(name, language)`` ------------------------------------- +:: -Adds new language to the library under the specified name. Used mostly internally. + unregisterLanguage(languageName) -* ``name``: a string with the name of the language being registered -* ``language``: a function that returns an object which represents the - language definition. The function is passed the ``hljs`` object to be able - to use common regular expressions defined within it. +Removes a language and its aliases from the library. Used mostly internally. +* ``languageName``: a string with the name of the language being removed. -``listLanguages()`` ----------------------------- -Returns the languages names list. +registerAliases +--------------- + +:: + + registerAliases(alias|aliases, {languageName}) + +Adds new language alias or aliases to the library for the specified language name defined under ``languageName`` key. +* ``alias|aliases``: a string or array with the name of alias being registered +* ``languageName``: the language name as specified by ``registerLanguage``. + + +listLanguages +------------- + +Returns the languages names list. .. _getLanguage: -``getLanguage(name)`` ---------------------- +getLanguage +----------- + +:: + + getLanguage(name) Looks up a language by name or alias. Returns the language object if found, ``undefined`` otherwise. -``requireLanguage(name)`` ---------------------- +versionString +------------- -Looks up a language by name or alias. +:: -This should be used when one language definition depends on another. -Using this function (vs ``getLanguage``) will provide better error messaging -when a required language is missing. + versionString -Returns the language object if found, raises a hard error otherwise. +Returns the full Highlight.js version as a string, ie: ``"11.0.1"``. -``debugMode()`` ---------------- +safeMode +-------- + +:: + + safeMode() + +Enables safe mode. This is the default mode, providing the most reliable experience for production usage. -Enables *debug/development* mode. **This mode purposely makes Highlight.js more fragile! It should only be used for testing and local development (of languages or the library itself).** By default "Safe Mode" is used, providing the most reliable experience for production usage. + +debugMode +--------- + +:: + + debugMode() + +Enables *debug/development* mode. + +.. warning:: + + This mode purposely makes Highlight.js more fragile! It should only be used for testing and local development (of languages or the library itself). For example, if a new version suddenly had a serious bug (or breaking change) that affected only a single language: -* **In Safe Mode**: All other languages would continue to highlight just fine. The broken language would appear as a code block, but without any highlighting (as if it were plaintext). -* **In Debug Mode**: All highlighting would stop when an error was encountered and a JavaScript error would be thrown. +* **In Safe Mode** all other languages would continue to highlight just fine. The broken language would appear as a code block, but without any highlighting (as if it were plaintext). +* **In Debug Mode** all highlighting would stop and a JavaScript error would be thrown. + + +Deprecated API +-------------- + +highlight +^^^^^^^^^ + +.. deprecated:: 10.7 This will be removed entirely in v12. + +:: + + highlight(languageName, code) + + +Please see the :ref:`newer API` shown above. + + +highlightBlock +^^^^^^^^^^^^^^ + +.. deprecated:: 11.0 Please use ``highlightElement()`` instead. + + +initHighlighting +^^^^^^^^^^^^^^^^ + +.. deprecated:: 10.6 Please use ``highlightAll()`` instead. + + + +initHighlightingOnLoad +^^^^^^^^^^^^^^^^^^^^^^ + +.. deprecated:: 10.6 Please use ``highlightAll()`` instead. + diff --git a/docs/building-testing.rst b/docs/building-testing.rst index 38303e9146..09bbc34235 100644 --- a/docs/building-testing.rst +++ b/docs/building-testing.rst @@ -1,9 +1,27 @@ -Building and testing +Building and Testing ==================== -To actually run highlight.js it is necessary to build it for the environment -where you're going to run it: a browser, the node.js server, etc. +To use Highlight.js it is first necessary to build it for the environment +where you plan to execute it: the browser, the Node.js server, etc. +TLDR +---- + +Often when contributing a pull-request it's sufficient to build and test only +the Node.js build. Our CI process will guarantee the browser build tests are all +still green if you don't run them during development. + +:: + + npm run build + npm run test + +The browser library must be built and tested separately: + +:: + + npm run build-browser + npm run test-browser Building -------- @@ -53,8 +71,8 @@ detection still works with your language definition included in the whole suite. Testing is done using `Mocha `_ and the files are found in the ``test/`` directory. You can use the node build to -run the tests in the command line with ``npm test`` after installing the -dependencies with ``npm install``. +run the tests from the command line with ``npm test`` after building_. (Using +``npm run build_and_test`` you can build and then test with one command.) **Note**: for Debian-based machine, like Ubuntu, you might need to create an alias or symbolic link for nodejs to node. The reason for this is the @@ -99,7 +117,7 @@ finish with your changes you can build the container from the root of the reposi docker build -t highlight-js . -And then run the container. You will need to expose port 80 on the host for the +And then run the container. You will need to expose port 80 on the host for the web interface, and note that we are running it in detached (-d) mode. :: @@ -140,14 +158,14 @@ for preview. When you are done, clean up your container. docker stop highlight-js -If you want a more advanced testing setup, you can bind the source folder when you +If you want a more advanced testing setup, you can bind the source folder when you run the container. :: docker run -d --name highlight-js --volume $PWD/src:/var/www/html/src --rm -p 80:80 highlight-js -Then if you want to make changes, you can do so locally (the folder is bound as a volume), +Then if you want to make changes, you can do so locally (the folder is bound as a volume), and execute a command to the container to trigger a rebuild: :: diff --git a/docs/conf.py b/docs/conf.py index 49a32b7787..95fb287478 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -12,6 +12,8 @@ # serve to show the default. import sys, os +import sphinx_rtd_theme +import myst_parser # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the @@ -25,7 +27,10 @@ # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = [] +extensions = [ + "sphinx_rtd_theme", + "myst_parser" +] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] @@ -41,16 +46,16 @@ # General information about the project. project = u'highlight.js' -copyright = u'2012–2018, Ivan Sagalaev' +copyright = u'2012–2022, Ivan Sagalaev' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. -# -# The short X.Y version. -version = '9.18' + # The full version, including alpha/beta/rc tags. -release = '9.18.0' +release = '11.4.0' +# The short X.Y version. +version = ".".join(release.split(".")[:2]) # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -91,7 +96,7 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'default' +html_theme = 'sphinx_rtd_theme' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the diff --git a/docs/css-classes-reference.rst b/docs/css-classes-reference.rst index a75e411a50..9cea743642 100644 --- a/docs/css-classes-reference.rst +++ b/docs/css-classes-reference.rst @@ -1,140 +1,228 @@ -CSS classes reference -===================== +Scope Reference +=============== -Stylable classes +Stylable Scopes ---------------- -+------------------------------------------------------------------------------+ -| **General-purpose** | -+--------------------------+---------------------------------------------------+ -| keyword | keyword in a regular Algol-style language | -+--------------------------+---------------------------------------------------+ -| built_in | built-in or library object (constant, class, | -| | function) | -+--------------------------+---------------------------------------------------+ -| type | user-defined type in a language with first-class | -| | syntactically significant types, like Haskell | -+--------------------------+---------------------------------------------------+ -| literal | special identifier for a built-in value ("true", | -| | "false", "null") | -+--------------------------+---------------------------------------------------+ -| number | number, including units and modifiers, if any. | -+--------------------------+---------------------------------------------------+ -| regexp | literal regular expression | -+--------------------------+---------------------------------------------------+ -| string | literal string, character | -+--------------------------+---------------------------------------------------+ -| subst | parsed section inside a literal string | -+--------------------------+---------------------------------------------------+ -| symbol | symbolic constant, interned string, goto label | -+--------------------------+---------------------------------------------------+ -| class | class or class-level declaration (interfaces, | -| | traits, modules, etc) | -+--------------------------+---------------------------------------------------+ -| function | function or method declaration | -+--------------------------+---------------------------------------------------+ -| title | name of a class or a function at the place of | -| | declaration | -+--------------------------+---------------------------------------------------+ -| params | block of function arguments (parameters) at the | -| | place of declaration | -+--------------------------+---------------------------------------------------+ -| **Meta** | -+--------------------------+---------------------------------------------------+ -| comment | comment | -+--------------------------+---------------------------------------------------+ -| doctag | documentation markup within comments | -+--------------------------+---------------------------------------------------+ -| meta | flags, modifiers, annotations, processing | -| | instructions, preprocessor directive, etc | -+--------------------------+---------------------------------------------------+ -| meta-keyword | keyword or built-in within meta construct | -+--------------------------+---------------------------------------------------+ -| meta-string | string within meta construct | -+--------------------------+---------------------------------------------------+ -| **Tags, attributes, configs** | -+--------------------------+---------------------------------------------------+ -| section | heading of a section in a config file, heading in | -| | text markup | -+--------------------------+---------------------------------------------------+ -| tag | XML/HTML tag | -+--------------------------+---------------------------------------------------+ -| name | name of an XML tag, the first word in an | -| | s-expression | -+--------------------------+---------------------------------------------------+ -| builtin-name | s-expression name from the language standard | -| | library | -+--------------------------+---------------------------------------------------+ -| attr | name of an attribute with no language defined | -| | semantics (keys in JSON, setting names in .ini), | -| | also sub-attribute within another highlighted | -| | object, like XML tag | -+--------------------------+---------------------------------------------------+ -| attribute | name of an attribute followed by a structured | -| | value part, like CSS properties | -+--------------------------+---------------------------------------------------+ -| variable | variable in a config or a template file, | -| | environment var expansion in a script | -+--------------------------+---------------------------------------------------+ -| **Markup** | -+--------------------------+---------------------------------------------------+ -| bullet | list item bullet in text markup | -+--------------------------+---------------------------------------------------+ -| code | code block in text markup | -+--------------------------+---------------------------------------------------+ -| emphasis | emphasis in text markup | -+--------------------------+---------------------------------------------------+ -| strong | strong emphasis in text markup | -+--------------------------+---------------------------------------------------+ -| formula | mathematical formula in text markup | -+--------------------------+---------------------------------------------------+ -| link | hyperlink in text markup | -+--------------------------+---------------------------------------------------+ -| quote | quotation in text markup | -+--------------------------+---------------------------------------------------+ -| **CSS** | -+--------------------------+---------------------------------------------------+ -| selector-tag | tag selector in CSS | -+--------------------------+---------------------------------------------------+ -| selector-id | #id selector in CSS | -+--------------------------+---------------------------------------------------+ -| selector-class | .class selector in CSS | -+--------------------------+---------------------------------------------------+ -| selector-attr | [attr] selector in CSS | -+--------------------------+---------------------------------------------------+ -| selector-pseudo | :pseudo selector in CSS | -+--------------------------+---------------------------------------------------+ -| **Templates** | -+--------------------------+---------------------------------------------------+ -| template-tag | tag of a template language | -+--------------------------+---------------------------------------------------+ -| template-variable | variable in a template language | -+--------------------------+---------------------------------------------------+ -| **diff** | -+--------------------------+---------------------------------------------------+ -| addition | added or changed line in a diff | -+--------------------------+---------------------------------------------------+ -| deletion | deleted line in a diff | +The general purpose scopes are intended to be used for any language, but the +other scopes may also be used when semantically correct. For example if you had +a general purpose language that allowed inline URLs: + +:: + + var GOOGLE = https://www.google.com/ + +It would be reasonable to use the ``link`` class for this, even if your language +is not a markup language. However, many themes might not be designed with this +in mind so a better choice (for best theme support) might possibly be ``string``. + ++----------------------------------------------------------------------------------------+ +| **General purpose** | ++--------------------------+-------------------------------------------------------------+ +| keyword | keyword in a regular Algol-style language | ++--------------------------+-------------------------------------------------------------+ +| built_in | built-in or library object (constant, class, | +| | function) | ++--------------------------+-------------------------------------------------------------+ +| type | data type (in a language with syntactically | +| | significant types) (``string``, ``int``, | +| | ``array``, etc.) | ++--------------------------+-------------------------------------------------------------+ +| literal | special identifier for a built-in value | +| | (``true``, ``false``, ``null``, etc.) | ++--------------------------+-------------------------------------------------------------+ +| number | number, including units and modifiers, if any. | ++--------------------------+-------------------------------------------------------------+ +| operator | operators: ``+``, ``-``, ``>>``, ``|``, ``==`` | ++--------------------------+-------------------------------------------------------------+ +| punctuation | aux. punctuation that should be subtly highlighted | +| | (parentheses, brackets, etc.) | ++--------------------------+-------------------------------------------------------------+ +| property | object property ``obj.prop1.prop2.value`` | ++--------------------------+-------------------------------------------------------------+ +| regexp | literal regular expression | ++--------------------------+-------------------------------------------------------------+ +| string | literal string, character | ++--------------------------+-------------------------------------------------------------+ +| char.escape | an escape character such as ``\n`` | ++--------------------------+-------------------------------------------------------------+ +| subst | parsed section inside a literal string | ++--------------------------+-------------------------------------------------------------+ +| symbol | symbolic constant, interned string, goto label | ++--------------------------+-------------------------------------------------------------+ +| class | **deprecated** You probably want ``title.class`` | ++--------------------------+-------------------------------------------------------------+ +| function | **deprecated** You probably want ``title.function`` | ++--------------------------+-------------------------------------------------------------+ +| variable | variables | ++--------------------------+-------------------------------------------------------------+ +| variable.language | variable with special meaning in a language, e.g.: | +| | ``this``, ``window``, ``super``, ``self``, etc. | ++--------------------------+-------------------------------------------------------------+ +| variable.constant | variable that is a constant value, ie ``MAX_FILES`` | ++--------------------------+-------------------------------------------------------------+ +| title | name of a class or a function | ++--------------------------+-------------------------------------------------------------+ +| title.class | name of a class (interface, trait, module, etc) | ++--------------------------+-------------------------------------------------------------+ +| title.class.inherited | name of class being inherited from, extended, etc. | ++--------------------------+-------------------------------------------------------------+ +| title.function | name of a function | ++--------------------------+-------------------------------------------------------------+ +| title.function.invoke | name of a function (when being invoked) | ++--------------------------+-------------------------------------------------------------+ +| params | block of function arguments (parameters) at the | +| | place of declaration | ++--------------------------+-------------------------------------------------------------+ +| comment | comments | ++--------------------------+-------------------------------------------------------------+ +| doctag | documentation markup within comments, e.g. ``@params`` | ++--------------------------+-------------------------------------------------------------+ +| **Meta** | ++--------------------------+-------------------------------------------------------------+ +| meta | flags, modifiers, annotations, processing | +| | instructions, preprocessor directives, etc | ++--------------------------+-------------------------------------------------------------+ +| meta keyword | a keyword inside a meta block | +| | (note this is nested, not subscoped) | ++--------------------------+-------------------------------------------------------------+ +| meta string | a string inside a meta block | +| | (note this is nested, not subscoped) | ++--------------------------+-------------------------------------------------------------+ +| **Tags, attributes, configs** | ++--------------------------+-------------------------------------------------------------+ +| section | heading of a section in a config file, heading in | +| | text markup | ++--------------------------+-------------------------------------------------------------+ +| tag | XML/HTML tag | ++--------------------------+-------------------------------------------------------------+ +| name | name of an XML tag, the first word in an | +| | s-expression | ++--------------------------+-------------------------------------------------------------+ +| attr | name of an attribute with no language defined | +| | semantics (keys in JSON, setting names in .ini), | +| | also sub-attribute within another highlighted | +| | object, like XML tag | ++--------------------------+-------------------------------------------------------------+ +| attribute | name of an attribute followed by a structured | +| | value part, like CSS properties | ++--------------------------+-------------------------------------------------------------+ +| **Text Markup** | ++--------------------------+-------------------------------------------------------------+ +| bullet | list item bullet | ++--------------------------+-------------------------------------------------------------+ +| code | code block | ++--------------------------+-------------------------------------------------------------+ +| emphasis | emphasis | ++--------------------------+-------------------------------------------------------------+ +| strong | strong emphasis | ++--------------------------+-------------------------------------------------------------+ +| formula | mathematical formula | ++--------------------------+-------------------------------------------------------------+ +| link | hyperlink | ++--------------------------+-------------------------------------------------------------+ +| quote | quotation or blockquote | ++--------------------------+-------------------------------------------------------------+ +| **CSS** | ++--------------------------+-------------------------------------------------------------+ +| selector-tag | tag selector | ++--------------------------+-------------------------------------------------------------+ +| selector-id | #id selector | ++--------------------------+-------------------------------------------------------------+ +| selector-class | .class selector | ++--------------------------+-------------------------------------------------------------+ +| selector-attr | [attr] selector | ++--------------------------+-------------------------------------------------------------+ +| selector-pseudo | :pseudo selector | ++--------------------------+-------------------------------------------------------------+ +| **Templates** | ++--------------------------+-------------------------------------------------------------+ +| template-tag | tag of a template language | ++--------------------------+-------------------------------------------------------------+ +| template-variable | variable in a template language | ++--------------------------+-------------------------------------------------------------+ +| **diff** | ++--------------------------+-------------------------------------------------------------+ +| addition | added or changed line | ++--------------------------+-------------------------------------------------------------+ +| deletion | deleted line | ++--------------------------+-------------------------------------------------------------+ + +A note on scopes with sub-scopes +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Some scope names above have a ``.`` in them. We use this notation to specify +sub-scopes. In the generated HTML this will output multiple computed class +names. The depth of nesting determines the number of underscores appended to +sub-scope names. For example, Lets say the scope is ``title.class.other``. + +The CSS class names generated would be: + +- ``hljs-title`` +- ``class_`` +- ``other__`` + +The top-level scope is always the one that has the configured prefix applied. + +The generated HTML would be: + +:: + + Render + +A theme could then simply target that using the following CSS: + +.. code-block:: css + + .hljs-title.class_.other__ { + color: blue; + } + + +A note on newer scopes +^^^^^^^^^^^^^^^^^^^^^^ + +Some scopes have been added more recently and do not enjoy universal theme +support. For themes without support, these items will simply not be +highlighted. This doesn't mean not to use them, only that they will be +highlighted better as support improves over time. + +A list of these scopes: + +- operator +- punctuation +- property + + +Reserved scopes +^^^^^^^^^^^^^^^ + +The below scopes (ReasonML) are left here for documentation purposes but may +not be used in other grammars because they are very poorly supported by all +themes. + +If you'd like to help out with the larger issue here: + +- https://github.com/highlightjs/highlight.js/issues/2521 +- https://github.com/highlightjs/highlight.js/issues/2500 + +--------------------------+---------------------------------------------------+ | **ReasonML** | +--------------------------+---------------------------------------------------+ -| operator | reasonml operator such as pipe | -+--------------------------+---------------------------------------------------+ -| pattern-match | reasonml pattern matching matchers | +| pattern-match | pattern matching matchers | +--------------------------+---------------------------------------------------+ | typing | type signatures on function parameters | +--------------------------+---------------------------------------------------+ | constructor | type constructors | +--------------------------+---------------------------------------------------+ -| module-access | scope access into a ReasonML module | +| module-access | scope access into a module | +--------------------------+---------------------------------------------------+ -| module | ReasonML module reference within scope access | +| module | module reference within scope access | +--------------------------+---------------------------------------------------+ Language names and aliases -------------------------- -The language names and aliases table has moved to `the project -README `_. +The language names and aliases table has moved to `SUPPORTED_LANGUAGES.md `_. diff --git a/docs/index.rst b/docs/index.rst index 3792e16245..899cc3df68 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -3,44 +3,53 @@ You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. -``highlight.js`` developer documentation -========================================== +Highlight.js Documentation +========================== + +.. toctree:: + :caption: Getting Started + :maxdepth: 1 -Contents: + + README / How to Use + building-testing + Upgrading to Version 11 .. toctree:: + :caption: Reference :maxdepth: 1 api + plugin-api + language-guide - reference + theme-guide + mode-reference css-classes-reference - style-guide - plugin-api plugin-recipes - language-contribution - building-testing - maintainers-guide - -Miscellaneous: +.. maintainers-guide .. toctree:: - :maxdepth: 1 + :caption: FAQ + :titlesonly: + :maxdepth: 0 - line-numbers - language-requests + Which languages are supported? + Why no line numbers? + How to request a new language? + How to contribute a new language? -Links: -- Code: https://github.com/highlightjs/highlight.js -- Discussion: http://groups.google.com/group/highlightjs -- Bug tracking: https://github.com/highlightjs/highlight.js/issues +Links +^^^^^ +.. hlist:: -Indices and tables -================== + - `Source `_ + - `Issues `_ + - `Discord `_ + - `Website `_ + - `highlight.js npm `_ + - `@highlightjs/cdn-assets npm `_ -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` diff --git a/docs/language-contribution.rst b/docs/language-contribution.rst index 614e816339..4fb9956b60 100644 --- a/docs/language-contribution.rst +++ b/docs/language-contribution.rst @@ -1,27 +1,61 @@ Language contributor checklist ============================== -1. Put language definition into a .js file ------------------------------------------- +1. Know that you are creating a 3rd party grammar +------------------------------------------------- + +*Sadly, due to lack of maintainer time we no longer merge new languages grammars +into the core library.* Instead, the project works by encouraging 3rd party +language grammar development by willing contributors ready to help and maintain +them. We're also happy to host those 3rd party language grammars at the +``highlightjs`` GitHub organization. Or you're welcome to host yourself - we're +always happy to link to to new language grammars. + +We also make it easy to build, maintain, and distribute 3rd party grammar +modules so that Highlight.js can always be easily extended with new languages. +Using a 3rd party language (for end users) is often as simple as just adding a +single line of code to their JavaScript or build system. + +We'd love to have your grammar as a 3rd party language module if you'd be +willing to maintain it over time. It's easy to develop and publish a 3rd party +language module. If you already started work in the main source tree - it's +trivial to convert your existing work into a 3rd party module. (you pretty much +just move your files into a new project folder) + + +2. Read extra/3RD_PARTY_QUICK_START.md +-------------------------------------- + +Next, read ``extra/3RD_PARTY_QUICK_START.md``. This should provide you with a +very high-level overview of the steps for creating a third-party language +grammar for Highlight.js. + + +3. Create a language grammar definition file +-------------------------------------------- The file defines a function accepting a reference to the library and returning a language object. -The library parameter is useful to access common modes and regexps. You should not immediately call this function, -this is done during the build process and details differ for different build targets. +The library parameter (``hljs``) is useful to access common modes and regexps. You should not +immediately call this function (you only need to export it). Calling it is handled by the build +process and details differ for different build targets. :: - function(hljs) { + export default function(hljs) { return { + name: "Language Name", keywords: 'foo bar', contains: [ ..., hljs.NUMBER_MODE, ... ] } } -The name of the file is used as a short language identifier and should be usable as a class name in HTML and CSS. +The name of the file is used as a short language identifier and should be usable +as a class name in HTML and CSS. Typically you'll place this file in your +new grammar repository under ``my_new_grammar/src/languages/``. -2. Provide meta data --------------------- +4. Add language metadata +---------------------------- At the top of the file there is a specially formatted comment with meta data processed by a build system. Meta data format is simply key-value pairs each occupying its own line: @@ -34,6 +68,7 @@ Meta data format is simply key-value pairs each occupying its own line: Author: John Smith Contributors: Mike Johnson <...@...>, Matt Wilson <...@...> Description: Some cool language definition + Website: https://superlanguage.example.com */ ``Language`` — the only required header giving a human-readable language name. @@ -44,10 +79,10 @@ Required files aren't processed in any special way. The build system just makes sure that they will be in the final package in ``LANGUAGES`` object. -The meaning of the other headers is pretty obvious. +The meaning of the other headers should be pretty obvious. -3. Create a code example +5. Create a code example ------------------------ The code example is used both to test language detection and for the demo page @@ -57,21 +92,27 @@ Take inspiration from other languages in ``test/detect/`` and read :ref:`testing instructions ` for more details. -4. Write class reference ------------------------- +6. Write a class reference if your class uses custom classes +------------------------------------------------------------ + +A class reference document should typically be placed at the root of your +language repository: ``css-class-reference.md`` -Class reference lives in the :doc:`CSS classes reference `.. Describe shortly names of all meaningful modes used in your language definition. +Note: If you use custom classes please be aware that all the build-in themes +are not going to support your custom classes and you should likely also +distribute your own custom theme. + -5. Add yourself to AUTHORS.*.txt and CHANGES.md ------------------------------------------------ +7. Request a repository at the ``highlightjs`` organization +----------------------------------------------------------- -If you're a new contributor add yourself to the authors list. -Also it will be good to update CHANGES.md. +*This is optional.* Of course you are free to host your repository anywhere +you would like. -6. Create a pull request +8. Create a pull request ------------------------ -Send your contribution as a pull request on GitHub. +Submit a PR to add your language to `SUPPORTED_LANGUAGES.md`. diff --git a/docs/language-guide.rst b/docs/language-guide.rst index 0971524e52..460ab70179 100644 --- a/docs/language-guide.rst +++ b/docs/language-guide.rst @@ -1,14 +1,16 @@ -Language definition guide +.. highlight:: javascript + +Language Definition Guide ========================= Highlighting overview --------------------- Programming language code consists of parts with different rules of parsing: keywords like ``for`` or ``if`` -don't make sense inside strings, strings may contain backslash-escaped symbols like ``\"`` +don't make sense inside strings, strings may contain backslash-escaped symbols like ``\"``, and comments usually don't contain anything interesting except the end of the comment. -In highlight.js such parts are called "modes". +In Highlight.js such parts are called "modes". Each mode consists of: @@ -19,7 +21,7 @@ Each mode consists of: * …exotic stuff like another language inside a language The parser's work is to look for modes and their keywords. -Upon finding, it wraps them into the markup ``...`` +Upon finding them, it wraps them into the markup ``...`` and puts the name of the mode ("string", "comment", "number") or a keyword group name ("keyword", "literal", "built-in") as the span's class name. @@ -39,7 +41,7 @@ Here's an example: keywords: 'for if while', contains: [ { - className: 'string', + scope: 'string', begin: '"', end: '"' }, hljs.COMMENT( @@ -48,7 +50,7 @@ Here's an example: { contains: [ { - className: 'doc', begin: '@\\w+' + scope: 'doc', begin: '@\\w+' } ] } @@ -64,40 +66,52 @@ and most interesting parsing happens inside tags. Keywords -------- -In the simple case language keywords are defined in a string, separated by space: +In the simple case language keywords can be defined with a string (space delimited) or array: :: { - keywords: 'else for if while' + keywords: 'else for if while', + // or with an array + keywords: ['else', 'for', 'if', 'while'] } -Some languages have different kinds of "keywords" that might not be called as such by the language spec -but are very close to them from the point of view of a syntax highlighter. These are all sorts of "literals", "built-ins", "symbols" and such. -To define such keyword groups the attribute ``keywords`` becomes an object each property of which defines its own group of keywords: +Some languages have different kinds of "keywords" that might not be called as +such by the language spec but are very close to them from the point of view of a +syntax highlighter. These are all sorts of "literals", "built-ins", "symbols" +and such. To define such keyword groups, the attribute ``keywords`` becomes an +object, each property of which defines its own group of keywords: :: { keywords: { keyword: 'else for if while', - literal: 'false true null' + literal: ['false','true','null'], + _relevance_only: 'one two three four' } } -The group name becomes then a class name in a generated markup enabling different styling for different kinds of keywords. +The group name becomes the class name in the generated markup, enabling different +theming for different kinds of keywords. Any property starting with a ``_`` will +only use those keywords to increase relevance, they will not be highlighted. -To detect keywords highlight.js breaks the processed chunk of code into separate words — a process called lexing. -The "word" here is defined by the regexp ``[a-zA-Z][a-zA-Z0-9_]*`` that works for keywords in most languages. -Different lexing rules can be defined by the ``lexemes`` attribute: +To detect keywords, highlight.js breaks the processed chunk of code into separate +words — a process called lexing. By default, "words" are matched with the regexp +``\w+``, and that works well for many languages. Different lexing rules can be +defined by the magic ``$pattern`` attribute: :: { - lexemes: '-[a-z]+', - keywords: '-import -export' + keywords: { + $pattern: /-[a-z]+/, // allow keywords to begin with dash + keyword: '-import -export' + } } +Note: The older ``lexemes`` setting has been deprecated in favor of using +``keywords.$pattern``. They are functionally identical. Sub-modes --------- @@ -121,8 +135,8 @@ This is commonly used to define nested modes: :: { - className: 'object', - begin: '{', end: '}', + scope: 'object', + begin: /\{/, end: /\}/, contains: [hljs.QUOTE_STRING_MODE, 'self'] } @@ -149,24 +163,24 @@ Parameters for the function are: Markup generation ----------------- -Modes usually generate actual highlighting markup — ```` elements with specific class names that are defined by the ``className`` attribute: +Modes usually generate actual highlighting markup — ```` elements with specific class names that are defined by the ``scope`` attribute: :: { contains: [ { - className: 'string', + scope: 'string', // ... other attributes }, { - className: 'number', + scope: 'number', // ... } ] } -Names are not required to be unique, it's quite common to have several definitions with the same name. +Scopes are not required to be unique; it's quite common to have several definitions with the same scope. For example, many languages have various syntaxes for strings, comments, etc… Sometimes modes are defined only to support specific parsing rules and aren't needed in the final markup. @@ -175,18 +189,21 @@ A classic example is an escaping sequence inside strings allowing them to contai :: { - className: 'string', + scope: 'string', begin: '"', end: '"', contains: [{begin: '\\\\.'}], } -For such modes ``className`` attribute should be omitted so they won't generate excessive markup. +For such modes, the ``scope`` attribute should be omitted so they won't generate excessive markup. + +For a list of all supported scope names please see the :doc:`Scopes Reference +`. Mode attributes --------------- -Other useful attributes are defined in the :doc:`mode reference `. +Other useful attributes are defined in the :doc:`mode reference `. .. _relevance: @@ -206,7 +223,7 @@ So these string modes are given high relevance: :: { - className: 'string', + scope: 'string', begin: 'r"', end: '"', relevance: 10 } @@ -217,12 +234,17 @@ and it makes sense to bring their relevance to zero to lessen statistical noise: :: { - className: 'string', + scope: 'string', begin: '"', end: '"', relevance: 0 } -The default value for relevance is 1. When setting an explicit value it's recommended to use either 10 or 0. +The default value for relevance is always 1. When setting an explicit value +typically either 10 or 0 is used. A 0 means this match should not be considered +for language detection purposes. 0 should be used for very common matches that +might be found in ANY language (basic numbers, strings, etc) or things that +would otherwise create too many false positives. A 10 means "this is almost +guaranteed to be XYZ code". 10 should be used sparingly. Keywords also influence relevance. Each of them usually has a relevance of 1, but there are some unique names that aren't likely to be found outside of their languages, even in the form of variable names. @@ -240,15 +262,15 @@ Illegal symbols --------------- Another way to improve language detection is to define illegal symbols for a mode. -For example in Python first line of class definition (``class MyClass(object):``) cannot contain symbol "{" or a newline. -Presence of these symbols clearly shows that the language is not Python and the parser can drop this attempt early. +For example, in Python the first line of a class definition (``class MyClass(object):``) cannot contain the symbol ``{`` or a newline. +The presence of these symbols clearly shows that the language is not Python, and the parser can drop this attempt early. -Illegal symbols are defined as a a single regular expression: +Illegal symbols are defined using a single regular expression: :: { - className: 'class', + scope: 'class', illegal: '[${]' } @@ -256,26 +278,28 @@ Illegal symbols are defined as a a single regular expression: Pre-defined modes and regular expressions ----------------------------------------- -Many languages share common modes and regular expressions. Such expressions are defined in core highlight.js code -at the end under "Common regexps" and "Common modes" titles. Use them when possible. +Many languages share common modes and regular expressions. These expressions are defined in `lib/modes.js `_ and should be used whenever possible. Regular Expression Features --------------------------- -The goal of Highlight.js is to support whatever regex features Javascript itself supports. You're using real regular expressions, use them responsibly. That said, due to the design of the parser, there are some caveats. These are addressed below. +The goal of Highlight.js is to support whatever regex features our supported JavaScript runtimes universally support. You're using real regular expressions, use them responsibly. That said, due to the design of the parser, there are some caveats. These are addressed below. -Things we support now that we did not always: +Things we fully support now that we did not always: * look-ahead regex matching for `begin` (#2135) * look-ahead regex matching for `end` (#2237) * look-ahead regex matching for `illegal` (#2135) * back-references within your regex matches (#1897) -* look-behind matching (when JS supports it) for `begin` (#2135) -Things we currently know are still issues: +Things that technically would work, but we do not allow (because Safari does not support look-behind): + +* look-behind matching for `begin` (#2135) + +Things that are not supported because of issues with the parsing engine itself: -* look-behind matching (when JS supports it) for `end` matchers +* look-behind matching for `end` matchers Contributing diff --git a/docs/language-requests.rst b/docs/language-requests.rst index 4e4c2f0b61..b00df95a01 100644 --- a/docs/language-requests.rst +++ b/docs/language-requests.rst @@ -2,16 +2,27 @@ On requesting new languages =========================== This is a general answer to requests for adding new languages that appear from -time to time in the highlight.js issue tracker and discussion group. +time to time in the Highlight.js issue tracker and discussion group. - Highlight.js doesn't have a fundamental plan for implementing languages, - instead the project works by accepting language definitions from - interested contributors. There are also no rules at the moment forbidding - any languages from being added to the library, no matter how obscure or - weird. + Highlight.js does not have a fundamental plan for implementing new languages + - i.e., the core team doesn't usually develop new languages. The core team + instead focuses on parser development, bugs, and supporting the existing + languages. They also currently does not have time to review, merge and + maintain any additional languages (fixing bugs, dealing with issues, etc). + + Instead, the project works by encouraging 3rd party language grammars from + contributors willing to help develop and maintain them. We're also happy to + host those 3rd party language grammars at the ``highlightjs`` GitHub + organization - no matter how obscure or weird. Or you're welcome to host it + yourself - we're still happy to link to it. + + This means that *there's no point in requesting a new language without also + providing a 3rd party implementation* (we'll simply close "Please support + language Xyz" issues with a link to this explanation). If you'd like to see + a particular language available but cannot implement it, the best way to + make it happen is to find another developer interested in doing so. + + For more info on actually developing a language see our :doc:`language-guide`, + and for information on how to properly package your 3rd party language module + see :doc:`language-contribution`. - This means that there's no point in requesting a new language without - providing an implementation for it. If you want to see a particular language - included in highlight.js but cannot implement it, the best way to make it - happen is to get another developer interested in doing so. Here's our - :doc:`language-guide`. diff --git a/docs/line-numbers.rst b/docs/line-numbers.rst index 674542d4ed..a335d0bbf2 100644 --- a/docs/line-numbers.rst +++ b/docs/line-numbers.rst @@ -2,10 +2,9 @@ Line numbers ============ Highlight.js' notable lack of line numbers support is not an oversight but a -feature. Following is the explanation of this policy from the current project -maintainer (hey guys!): +feature. Following is an explanation from the original author: - One of the defining design principles for highlight.js from the start was + One of the defining design principles for Highlight.js from the start was simplicity. Not the simplicity of code (in fact, it's quite complex) but the simplicity of usage and of the actual look of highlighted snippets on HTML pages. Many highlighters, in my opinion, are overdoing it with such @@ -15,7 +14,7 @@ maintainer (hey guys!): reader from understanding it. This is why it's not a straightforward decision: this new feature will not - just make highlight.js better, it might actually make it worse simply by + just make Highlight.js better, it might actually make it worse simply by making it look more bloated in blog posts around the Internet. This is why I'm asking people to show that it's worth it. @@ -28,12 +27,11 @@ maintainer (hey guys!): are better is to set up some usability research on the subject. I doubt anyone would bother to do it. - Then there's maintenance. So far the core code of highlight.js is + Then there's maintenance. So far the core code of Highlight.js is maintained by only one person — yours truly. Inclusion of any new code in - highlight.js means that from that moment I will have to fix bugs in it, + Highlight.js means that from that moment I will have to fix bugs in it, improve it further, make it work together with the rest of the code, defend its design. And I don't want to do all this for the feature that I consider "evil" and probably will never use myself. -This position is `subject to discuss `_. -Also it doesn't stop anyone from forking the code and maintaining line-numbers implementation separately. +This doesn't stop anyone from forking the library or maintaining a line-numbers plugin. diff --git a/docs/maintainers-guide.rst b/docs/maintainers-guide.rst deleted file mode 100644 index 0ac3719e84..0000000000 --- a/docs/maintainers-guide.rst +++ /dev/null @@ -1,43 +0,0 @@ -Maintainer's guide -================== - - -Commit policy -------------- - -* Pull requests from outside contributors require a review from a maintainer. - -* Maintainers should avoid working on a master branch directly and create branches for everything. A code review from another maintainer is recommended but not required, use your best judgment. - - - -Release process ---------------- - -Releases (minor) typically happen on a 6-week schedule. - -For major/minor releases you'll be releasing from ``master``. For patch releases you'll be releasing from a stable branch, such as ``9-16-stable``. This allows ongoing development of new features to continue in isolation (in master) without those changes leaking into patch releases (which should focus only on fixing breaking changes). - -The goal being that minor version series always get more stable over time and that patch releases do not add features. - -* For patch releases: First switch to the associated stable branch (i.e., ``9-16-stable``) - -* Update CHANGES.md with everything interesting since the last update. - -* Update version numbers using the three-part x.y.z notation everywhere: - - * The header in CHANGES.md (this is where the site looks for the latest version number) - * ``"version"`` attribute in package.json - * ``"version"`` attribute in package-lock.json (run `npm install`) - * Two places in docs/conf.py (``version`` and ``release``) - -* Commit the version changes and tag the commit with the version number (``9.16.2``, no "v" prefix or anything like that) - -* For major/minor releases: Create a new ``[major]-[minor]-stable`` branch such as ``9-16-stable`` - -* Push the commit and the tags (``git push && git push --tags``) - - -Pushing the tag triggers the update process which can be monitored at http://highlightjs.org/api/release/ - -When something didn't work *and* it's fixable in code (version numbers mismatch, last minute patches, etc), simply make another release incrementing the third (revision) part of the version number. diff --git a/docs/mode-reference.rst b/docs/mode-reference.rst new file mode 100644 index 0000000000..960b999fd5 --- /dev/null +++ b/docs/mode-reference.rst @@ -0,0 +1,662 @@ +.. highlight:: javascript + +Mode Reference +============== + +**Data Types** + +Types of attributes values in this reference: + ++------------+----------------------------------------------------------------------------+ +| mode | A valid Highlight.js Mode (as defined by this very reference) | ++------------+----------------------------------------------------------------------------+ +| scope | A valid grammar scope: ``title.class.inherited`` | ++------------+----------------------------------------------------------------------------+ +| regexp | JavaScript regexp literal (recommended) or string representing a regexp. | +| | | +| | (note when using a string proper escaping is critical) | ++------------+----------------------------------------------------------------------------+ +| boolean | JavaScript boolean: ``true`` or ``false`` | ++------------+----------------------------------------------------------------------------+ +| string | JavaScript string | ++------------+----------------------------------------------------------------------------+ +| number | JavaScript number | ++------------+----------------------------------------------------------------------------+ +| object | JavaScript object: ``{ ... }`` | ++------------+----------------------------------------------------------------------------+ +| array | JavaScript array: ``[ ... ]`` | ++------------+----------------------------------------------------------------------------+ + + +Language Attributes +------------------- + +These attributes are only valid at the language level (ie, they many only exist on the top-most language object and have no meaning if specified in children modes). + + +name +^^^^ + +- **type**: string + +The canonical name of this language, ie "JavaScript", etc. + + +unicodeRegex +^^^^^^^^^^^^ + +- **type**: boolean + +Expresses whether the grammar in question uses Unicode (``u`` flag) regular expressions. +(defaults to false) + + +case_insensitive +^^^^^^^^^^^^^^^^ + +- **type**: boolean + +Case insensitivity of language keywords and regexps. Used only on the top-level mode. +(defaults to false) + + +aliases +^^^^^^^ + +- **type**: array of strings + +A list of additional names (besides the canonical one given by the filename) that can be used to identify a language in HTML classes and in a call to :ref:`getLanguage `. + + +classNameAliases +^^^^^^^^^^^^^^^^ + +- **type**: object + +A mapping table of any custom scope names your grammar uses and their supported equivalencies. Perhaps your language has a concept of "slots" that roughly correspond to variables in other languages. This allows you to write grammar code like: + +:: + + { + classNameAliases: { + slot: "variable", + "message-name": "string" + }, + contains: [ + { + scope: "slot", + begin: // ... + } + ] + } + +The final HTML output will render slots with a CSS class of ``hljs-variable``. This feature exists to make it easier for grammar maintainers to think in their own language when maintaining a grammar. + +For a list of all supported scope names please see the :doc:`Scopes Reference +`. + + +disableAutodetect +^^^^^^^^^^^^^^^^^ + +- **type**: boolean + +Disables autodetection for this language. +(defaults to false, meaning auto-detect is enabled) + + +compilerExtensions +^^^^^^^^^^^^^^^^^^ + +.. warning:: + + **This is heavily dependent upon compiler internals and may NOT be + stable from minor release to minor release.** *It is currently recommended + only for 1st party grammars.* + +- **type**: an array of compiler extensions ie: ``(mode, parentMode) -> {}`` + +This allows grammars to extend the mode compiler to add their own syntactic +sugar to make reading and writing grammars easier. The +intention is that we use grammars to "test" out new compiler extensions and if +they perform well promote them into the core library. + +mode + The incoming mode object + +parentMode + The parent mode of the mode (null for the top level language mode) + +For example lets look at a tiny well behaved extension to allow us to write +``match`` as sugar to better express the intent to "match a single thing, then +end mode". + +:: + + compilerExtensions: [ + (mode, _parentMode) => { + // first some quick sanity checks + if (!mode.match) return; + + // then check for users doing things that would make no sense + if (mode.begin || mode.end) throw new Error("begin & end are not supported with match"); + + // copy the match regex into begin + mode.begin = mode.match; + + // cleanup: delete our syntactic construct + delete mode.match; + } + ] + +Compiler extension functions return nothing. They are expected to mutate the +mode itself. + + + +Mode Attributes +--------------- + +className +^^^^^^^^^ + +.. deprecated:: 11.0 + + Use ``scope`` instead. + + +scope +^^^^^ + +.. versionadded:: 11.0 + +- **type**: scope + +The scope of a given mode. Scopes are converted to CSS class names in HTML markup. + +Multiple modes can have the same scope. This is useful when a language has multiple variants of syntax +for one thing like string in single or double quotes. + +:: + + { + scope: "title.function.call", + begin: /[a-z]+\(/ + } + + +See :doc:`scopes reference` for details on scopes and CSS classes. + +begin +^^^^^ + +- **type**: regexp or array of regexp + +Regular expression starting a mode. For example a single quote for strings or two forward slashes for C-style comments. +If absent, ``begin`` defaults to a regexp that matches anything, so the mode starts immediately. + +This may also be an array. See :ref:`beginScope`. + +.. _beginScope: + +beginScope +^^^^^^^^^^ + +.. versionadded:: 11.0 + +- **type**: scope +- **type**: numeric index of scopes (when ``begin`` is an array) + +This can be used to apply a scope to just the begin match portion. + +:: + + { + begin: /def/, + beginScope: "keyword" + } + +You can also use ``beginScope`` to individually highlight portions of the match +with different scopes by passing an array to ``begin``. + +:: + + { + begin: [ + /function!/, + /\s+/, + hljs.IDENT_RE + ], + beginScope: { + 1: "keyword", + 3: "title" + }, + } + +This would highlight ``function!`` as a ``keyword`` while highlighting the name +of the function as ``title``. The space(s) between would be matched, but not +highlighted. + +Note: Internally, each regular expression in the array becomes a capture group +inside a larger concatenated regex. If your regular expressions use capture +groups (or references) they will be auto-magically renumerated so that they +continue to work without any changes. + +For more info see issue `#3095 `_. + + +endScope +^^^^^^^^ + +.. versionadded:: 11.0 + +- **type**: scope +- **type**: numeric index of scopes (when ``end`` is an array) + +This has the same behavior as ``beginScope`` but applies to the content of the +``end`` match. + +:: + + { + begin: /FIRST/, + end: /LAST/, + endScope: "built_in" + } + + +match +^^^^^ + +.. versionadded:: 11.0 + +- **type**: regexp or array of regexp + +This is simply syntactic sugar for a ``begin`` when no ``end`` expression is +necessary. It may not be used with ``begin`` or ``end`` keys (that would make +no sense). It exists simply to help make grammars more readable. + +:: + + { + scope: "title", + match: /Fish/ + } + +This is equivalent to: + +:: + + { + scope: "title", + begin: /Fish/ + } + + +on:begin +^^^^^^^^ + +- **type**: callback (matchData, response) + +This callback is triggered the moment a begin match is detected. ``matchData`` includes the typical regex match data; the full match, match groups, etc. The ``response`` object is used to tell the parser how it should handle the match. It can be also used to temporarily store data. + +- ``response.data`` - a simple object data store. Can be used for building more complex rules where the end rule is dependent on the content of begin, etc. +- ``response.ignoreMatch()`` - pretend as if this match never happened. The mode is not entered. Continues trying subsequent modes in the current mode's ``contains`` list + +For an example of usage see ``END_SAME_AS_BEGIN`` in ``modes.js``. + + +end +^^^ + +- **type**: regexp + +Regular expression ending a mode. For example a single quote for strings or "$" (end of line) for one-line comments. + +It's often the case that a beginning regular expression defines the entire mode and doesn't need any special ending. +For example a number can be defined with ``begin: "\\b\\d+"`` which spans all the digits. + +If absent, ``end`` defaults to a regexp that matches anything, so the mode ends immediately (after possibly +matching any ``contains`` sub-modes). + +Sometimes a mode can end not by itself but implicitly with its containing (parent) mode. +This is achieved with :ref:`endsWithParent ` attribute. + + +on:end +^^^^^^ + +- **type**: callback (matchData, response) + +This callback is triggered the moment an end match is detected. ``matchData`` includes the typical regex match data; the full match, match groups, etc. The ``response`` object is used to tell the parser how it should handle the match. It can also be used to retrieve data stored from a `begin` callback. + +- ``response.data`` - a simple object data store. Can be used for building more complex rules where the end rule is dependent on the content of begin, etc. +- ``response.ignoreMatch()`` - pretend as if this match never happened. The mode is not entered. Continues trying subsequent modes in the current mode's ``contains`` list + +For an example of usage see ``END_SAME_AS_BEGIN`` in ``modes.js``. + + +beginKeywords +^^^^^^^^^^^^^ + +- **type**: string + +Used instead of ``begin`` for modes starting with keywords to avoid needless repetition: + +:: + + { + begin: '\\b(class|interface)\\b', + keywords: 'class interface' + } + +… can often be shortened to: + +:: + + { + beginKeywords: 'class interface' + } + +Unlike the :ref:`keywords ` attribute, this one allows only a simple list of space separated keywords. +If you do need additional features of ``keywords`` or you just need more keywords for this mode you may include ``keywords`` along with ``beginKeywords``. + +.. note:: + + ``beginKeywords`` also checks for a ``.`` before or after the keywords and will fail to match if one is found. + This is to avoid false positives for method calls or property accesses. + + Ex. ``class A { ... }`` would match while ``A.class == B.class`` would not. + +.. _endsWithParent: + +endsWithParent +^^^^^^^^^^^^^^ + +- **type**: boolean + +A flag indicating that a mode ends when its parent ends. + +This is best demonstrated by example. In CSS syntax a selector has a set of rules contained within symbols "{" and "}". +Individual rules are separated by ";" but the last rule may omit the terminating semicolon: + +:: + + p { + width: 100%; + color: red + } + +A simple ``end: /;/`` rule is problematic - the parser could get "stuck" looking +for a ``;`` that it will never find (or find much later) - skipping over valid content that should be +highlighted. This is where ``endsWithParent`` proves useful: + +:: + + { + scope: 'rules', begin: /\{/, end: /\}/, + contains: [ + {scope: 'rule', /* ... */ end: ';', endsWithParent: true} + ] + } + +The ``rule`` scope now will end when the parser sees *either* a ``;`` or a ``}`` (from the parent). + +.. _endsParent: + +endsParent +^^^^^^^^^^^^^^ + +- **type**: boolean + +Forces closing of the parent mode right after the current mode is closed. + +This is used for modes that don't have an easily expressible ending lexeme but +instead could be closed after the last interesting sub-mode is found. + +Here's an example with two ways of defining functions in Elixir, one using a +keyword ``do`` and another using a comma: + +:: + + def foo :clear, list do + :ok + end + + def foo, do: IO.puts "hello world" + +Note that in the first case the parameter list after the function title may also +include a comma. And if we're only interested in highlighting a title we can +tell it to end the function definition after itself: + +:: + + { + scope: 'function', + beginKeywords: 'def', end: hljs.MATCH_NOTHING_RE, + contains: [ + { + scope: 'title', + begin: hljs.IDENT_RE, endsParent: true + } + ] + } + +The ``end: hljs.MATCH_NOTHING_RE`` ensures that function will never end itself. + + +.. _keywords: + +keywords +^^^^^^^^ + +- **type**: object / string / array + +*Keyword definition comes in three forms.* + +A string of space-separated keywords with an optional relevance following a pipe (``|``): + +:: + + 'for while if|0 else weird_voodoo|10 ...' + +An array of keywords (with optional relevance following a ``|``): + + :: + + [ + "for", + "while", + "if|0" + ] + +.. note:: + + It's recommended that the array form be used (one keyword per line) rather + than a string to simplify future maintenance. This is the style followed by + grammars part of the core library. + + +An object that describing multiple sets of keywords and (optionally) the pattern +used to locate them: + +:: + + { + keyword: [ 'for', 'while', 'if|0' ], + literal: [ 'true', 'false' ], + $pattern: /\w+/ + } + + + +For a more detailed explanation see :doc:`Language definition guide `. + + +illegal +^^^^^^^ + +- **type**: regexp or array + +A regular expression or array that defines symbols illegal for the mode. When +the parser finds an illegal match it may immediately stop parsing the whole +language altogether (see ``ignoreIllegals``). Smart use of illegal can greatly +improve auto-detection by quickly ruling out a language (when an illegal match +is found). + +:: + + { + illegal: /%/, + // or using an array + illegal: [ /%/, /cookies/ ] + } + + +excludeBegin, excludeEnd +^^^^^^^^^^^^^^^^^^^^^^^^ + +- **type**: boolean + +Excludes beginning or ending matches from a mode's content. For example in CSS +syntax a rule ends with a semicolon. However visually it's better not to +consider the semicolon as part of the rule's contents. Using ``excludeEnd: +true`` forces a ```` element for the rule to close before the semicolon. + +The semicolon is still consumed by the rule though and cannot be matched by +other subsequent rules. (it's effectively been skipped over) + + +returnBegin +^^^^^^^^^^^ + +- **type**: boolean + +Returns just found beginning lexeme back into parser. This is used when beginning of a sub-mode is a complex expression +that should not only be found within a parent mode but also parsed according to the rules of a sub-mode. + +.. warning:: + + Since the parser is effectively goes back it's quite possible to create a infinite loop here so use with caution! + A look-ahead regex is almost always preferable. + + +returnEnd +^^^^^^^^^ + +- **type**: boolean + +Returns just found ending lexeme back into parser. This is used for example to parse JavaScript embedded into HTML. +A JavaScript block ends with the HTML closing tag ```` that cannot be parsed with JavaScript rules. +So it is returned back into its parent HTML mode that knows what to do with it. + +.. warning:: + + Since the parser is effectively goes back it's quite possible to create a infinite loop here so use with caution! + A look-ahead regex is almost always preferable. + + +contains +^^^^^^^^ + +- **type**: array + +The list of sub-modes that can be found inside the mode. For detailed explanation see :doc:`Language definition guide `. + + +starts +^^^^^^ + +- **type**: mode + +The the mode that will start right after the current mode ends. The new mode will not be contained within the current one. + +Currently this attribute is used to highlight JavaScript and CSS contained within HTML. +Tags ```` that cannot be parsed with Javascript rules. -So it is returned back into its parent HTML mode that knows what to do with it. - -Since the parser is effectively goes back it's quite possible to create a infinite loop here so use with caution! - - -contains -^^^^^^^^ - -**type**: array - -The list of sub-modes that can be found inside the mode. For detailed explanation see :doc:`Language definition guide `. - - -starts -^^^^^^ - -**type**: identifier - -The name of the mode that will start right after the current mode ends. The new mode won't be contained within the current one. - -Currently this attribute is used to highlight Javascript and CSS contained within HTML. -Tags `` + + diff --git a/test/markup/xml/unquoted-attributes.expect.txt b/test/markup/xml/unquoted-attributes.expect.txt index 621a51f6fc..98d69e8a02 100644 --- a/test/markup/xml/unquoted-attributes.expect.txt +++ b/test/markup/xml/unquoted-attributes.expect.txt @@ -1,9 +1,9 @@ -<img src="/pics/foo.jpg"> -<img src='/pics/foo.jpg'> +<img src="/pics/foo.jpg"> +<img src='/pics/foo.jpg'> <img src=/pics/foo.jpg> <img src=/pics/> <img src=/pics /> -<img alt=''/> +<img alt=''/> <img alt/> -<img alt=''> +<img alt=''> <img alt> diff --git a/test/markup/xquery/computed_inbuilt.expect.txt b/test/markup/xquery/computed_inbuilt.expect.txt index a902c95d5a..a6633178aa 100644 --- a/test/markup/xquery/computed_inbuilt.expect.txt +++ b/test/markup/xquery/computed_inbuilt.expect.txt @@ -1,9 +1,9 @@ -xquery version "3.1"; +xquery version "3.1"; let $root := element {fn:node-name($e)} {$e/@*, 2 * fn:data($e)} for $node in root($root) return - element root { root ($node)/text(), attribute root {'root'}, -element not-root{attribute type{"root"}, root($root)} + element root { root ($node)/text(), attribute root {'root'}, +element not-root{attribute type{"root"}, root($root)} } diff --git a/test/markup/xquery/direct_method.expect.txt b/test/markup/xquery/direct_method.expect.txt index 34b756fdd1..7aa6503e21 100644 --- a/test/markup/xquery/direct_method.expect.txt +++ b/test/markup/xquery/direct_method.expect.txt @@ -1,12 +1,12 @@ -xquery version "3.1"; -let $var := <root n="x1">"rooting" out 1 or 2 root causes</root> +xquery version "3.1"; +let $var := <root n="x1">"rooting" out 1 or 2 root causes</root> return - <result name="test"> - disable highlight for a name such as root { + <result name="test"> + disable highlight for a name such as root { for $name in $var return $name as xs:string - } + } return to unhighlighted order of things. - <test type="{$name}">"rooting" out root causes</test> + <test type="{$name}">"rooting" out root causes</test> </result> diff --git a/test/markup/xquery/function_body.expect.txt b/test/markup/xquery/function_body.expect.txt index a8eb56b69b..c929d936a8 100644 --- a/test/markup/xquery/function_body.expect.txt +++ b/test/markup/xquery/function_body.expect.txt @@ -2,10 +2,10 @@ for $n in $node return element div { switch($n) - case 'abc' return 'OK' + case 'abc' return 'OK' default return 2 } }; for $x in 1 to 3 return - local:test(<test>abc</test>) + local:test(<test>abc</test>) diff --git a/test/markup/xquery/prolog_declarations.expect.txt b/test/markup/xquery/prolog_declarations.expect.txt index 08f101c7b3..b98cd127d8 100644 --- a/test/markup/xquery/prolog_declarations.expect.txt +++ b/test/markup/xquery/prolog_declarations.expect.txt @@ -1,22 +1,22 @@ -xquery version "3.1"; +xquery version "3.1"; (:~ : @author Duncan Paterson : @version 1.0:) -module namespace app="http://none"; +module namespace app="http://none"; -import module namespace config="http://config" at "config.xqm"; (: schema :) +import module namespace config="http://config" at "config.xqm"; (: schema :) declare copy-namespaces no-preserve, inherit; (: switch to preserve, no-inherit:) declare %private variable $app:maxItems := 12; -declare context item := doc("catalog.xml"); +declare context item := doc("catalog.xml"); declare %templates:wrap-all function app:helloworld($node as node(), $model as map(*), $name as xs:string?) { if ($name) then - <p>Hello {$name}!</p> + <p>Hello {$name}!</p> else () }; diff --git a/test/markup/yaml/inline.expect.txt b/test/markup/yaml/inline.expect.txt new file mode 100644 index 0000000000..d12626f0ae --- /dev/null +++ b/test/markup/yaml/inline.expect.txt @@ -0,0 +1,3 @@ +foo: [bar, bar2, [1, 2], 3] +foo: {bar: [1, 2], baz: {inside: 3}} +foo: ba{}r,ba[]z diff --git a/test/markup/yaml/inline.txt b/test/markup/yaml/inline.txt new file mode 100644 index 0000000000..8dfa15b2ea --- /dev/null +++ b/test/markup/yaml/inline.txt @@ -0,0 +1,3 @@ +foo: [bar, bar2, [1, 2], 3] +foo: {bar: [1, 2], baz: {inside: 3}} +foo: ba{}r,ba[]z diff --git a/test/markup/yaml/keys.expect.txt b/test/markup/yaml/keys.expect.txt index e5fb704eee..4d996f5894 100644 --- a/test/markup/yaml/keys.expect.txt +++ b/test/markup/yaml/keys.expect.txt @@ -7,11 +7,11 @@ some key: another key: value -"some key": - "another key": value +"some key": + "another key": value -'some key': - 'another key': value +'some key': + 'another key': value some-key: another-key: value diff --git a/test/markup/yaml/numbers.expect.txt b/test/markup/yaml/numbers.expect.txt index 969ba53024..2454a1fc4c 100644 --- a/test/markup/yaml/numbers.expect.txt +++ b/test/markup/yaml/numbers.expect.txt @@ -3,3 +3,8 @@ hex: 0x999fff numkey999: 1234 exp: -2.3e-5 +canonical: 2001-12-15T02:59:43.1Z +iso8601: 2001-12-14t21:59:43.10-05:00 +space: 2001-12-14 21:59:43.10 -5 +nozone: 2001-12-15 2:59:43.10 +date: 2002-12-14 diff --git a/test/markup/yaml/numbers.txt b/test/markup/yaml/numbers.txt index 7b6c10e2b4..7d39a8eab1 100644 --- a/test/markup/yaml/numbers.txt +++ b/test/markup/yaml/numbers.txt @@ -3,3 +3,8 @@ not_hex: 999fff hex: 0x999fff numkey999: 1234 exp: -2.3e-5 +canonical: 2001-12-15T02:59:43.1Z +iso8601: 2001-12-14t21:59:43.10-05:00 +space: 2001-12-14 21:59:43.10 -5 +nozone: 2001-12-15 2:59:43.10 +date: 2002-12-14 diff --git a/test/markup/yaml/string.expect.txt b/test/markup/yaml/string.expect.txt index 4980e5c938..84899f239b 100644 --- a/test/markup/yaml/string.expect.txt +++ b/test/markup/yaml/string.expect.txt @@ -1,6 +1,6 @@ key: value -key: 'some value' -key: "some value" +key: 'some value' +key: "some value" key: | multi-string value diff --git a/test/markup/yaml/tag.expect.txt b/test/markup/yaml/tag.expect.txt index dbc5645dcd..d777a9b7ac 100644 --- a/test/markup/yaml/tag.expect.txt +++ b/test/markup/yaml/tag.expect.txt @@ -1,4 +1,12 @@ key: !!builtintagname test key: !localtagname test -key: "!notatag" -key: '!!notatageither' +key: "!notatag" +key: '!!notatageither' +key: !!python/dict test +key: !!python/name:module.name test +key: !foo2.bar test +key: !(foo.bar?):tag test +key: !named!tag test + +--- !<tag:clarkevans.com,2002:invoice> +invoice: 34843 diff --git a/test/markup/yaml/tag.txt b/test/markup/yaml/tag.txt index 35f361543d..20ee84a731 100644 --- a/test/markup/yaml/tag.txt +++ b/test/markup/yaml/tag.txt @@ -2,3 +2,11 @@ key: !!builtintagname test key: !localtagname test key: "!notatag" key: '!!notatageither' +key: !!python/dict test +key: !!python/name:module.name test +key: !foo2.bar test +key: !(foo.bar?):tag test +key: !named!tag test + +--- ! +invoice: 34843 diff --git a/test/markup/zephir/default.expect.txt b/test/markup/zephir/default.expect.txt index 099d29299a..afd8b2e3fc 100644 --- a/test/markup/zephir/default.expect.txt +++ b/test/markup/zephir/default.expect.txt @@ -28,7 +28,7 @@ // See fn is allowed like shortcut public fn method2() -> <Test> { - call_user_func(function() { echo "hello"; }); + call_user_func(function() { echo "hello"; }); [1, 2, 3, 4, 5]->walk( diff --git a/test/parser/beginEndScope.js b/test/parser/beginEndScope.js new file mode 100644 index 0000000000..e2cf9af7b8 --- /dev/null +++ b/test/parser/beginEndScope.js @@ -0,0 +1,72 @@ +'use strict'; + +const hljs = require('../../build'); +hljs.debugMode(); + +describe('beginScope and endScope', () => { + before(() => { + const grammar = function() { + return { + contains: [ + { + begin: /xyz/, + end: /123/, + scope: "string", + beginScope: "red", + endScope: "green" + }, + { + begin: /123/, + end: [ /a/,/((b))/,/c/,/d/ ], + endScope: { 1: "apple", 2: "boy", 4: "delta" } + }, + { + begin: /dumb/, + end: /luck/, + beginScope: "red", + endScope: "green" + }, + { + begin: /abc/, + beginScope: "letters", + contains: [ + { match: /def/, scope: "more" } + ] + } + ] + } + }; + hljs.registerLanguage("test", grammar); + }); + after(() => { + hljs.unregisterLanguage("test"); + }); + it('should support multi-class', () => { + const code = "123 abcd"; + const result = hljs.highlight(code, { language: 'test' }); + + result.value.should.equal(`123 abcd`); + }) + it('should support an outer scope wrapper', () => { + const code = "xyz me 123"; + const result = hljs.highlight(code, { language: 'test' }); + + result.value.should.equal( + `` + + `xyz me 123` + + ``); + }) + it('should support textual beginScope & endScope pair', () => { + const code = "dumb really luck"; + const result = hljs.highlight(code, { language: 'test' }); + + result.value.should.equal(`dumb really luck`); + }); + it('should support textual beginScope', () => { + const code = "abcdef"; + const result = hljs.highlight(code, { language: 'test' }); + + result.value.should.equal(`abcdef`); + }); + +}); diff --git a/test/parser/compiler-extensions.js b/test/parser/compiler-extensions.js new file mode 100644 index 0000000000..7ba26e0199 --- /dev/null +++ b/test/parser/compiler-extensions.js @@ -0,0 +1,52 @@ +const hljs = require('../../build'); + +// not quite ready to become a plugin yet, so these hooks +// have been removed and we're skipping this test for now +describe.skip("compiler extension plugins", function() { + before(function() { + hljs.debugMode(); + hljs.registerLanguage("extension_test", function(hljs) { + return { + name: "test", + contains: [ + { earlyWantsToBeBegin: "booger", apple: true }, + { lateWantsToBeBegin: "booger" } + ] + }; + }); + const plugin = { + "before:compileEarly": (mode, parent) => { + if (mode.earlyWantsToBeBegin) mode.begin = mode.earlyWantsToBeBegin; + if (mode.apple) mode.orange = true; + }, + "before:compileLate": (mode, parent) => { + if (mode.lateWantsToBeBegin) mode.begin = mode.lateWantsToBeBegin; + if (mode.orange) mode.lime = true; + } + }; + + hljs.addPlugin(plugin); + // stub highlight to make sure the language gets compiled + // since we have no API point to make that happen + hljs.highlight("", { language: "extension_test" }); + const [first, second] = hljs.getLanguage("extension_test").contains; + this.first = first; + this.second = second; + }); + + describe("triggered using a plugin", function() { + it("before:compileEarly is executed", function() { + this.first.begin.should.equal("booger"); + }); + + it("before:compileLate is executed", function() { + this.second.begin.should.equal("booger"); + }); + + it("should run early extensions first, then late", function() { + // early rule changes apple to orange + // late rule change orange to lime + this.first.lime.should.equal(true); + }); + }); +}); diff --git a/test/parser/index.js b/test/parser/index.js index 1472a03191..5e436a572d 100644 --- a/test/parser/index.js +++ b/test/parser/index.js @@ -1,7 +1,12 @@ 'use strict'; +const should = require('should'); + describe('hljs', function() { + require('./look-ahead-end-matchers'); + require('./resume-scan'); require('./reuse-endsWithParent'); require('./should-not-destroyData'); - require('./look-ahead-end-matchers'); + require('./compiler-extensions'); + require('./max_keyword_hits'); }); diff --git a/test/parser/look-ahead-end-matchers.js b/test/parser/look-ahead-end-matchers.js index 241e1fd0f6..5d96e1d8ef 100644 --- a/test/parser/look-ahead-end-matchers.js +++ b/test/parser/look-ahead-end-matchers.js @@ -20,7 +20,7 @@ describe("parser specifics", function () { }; }); - hljs.highlight('test-language', 'ABC123 is the secret. XYZ123. End of string: ABC123').value + hljs.highlight('ABC123 is the secret. XYZ123. End of string: ABC123', {language: 'test-language'}).value .should.equal( // one full match at beginning, other match begins with XYZ but then never terminates, // so the end of the parsing finally closes the span tag diff --git a/test/parser/max_keyword_hits.js b/test/parser/max_keyword_hits.js new file mode 100644 index 0000000000..3fabe3a975 --- /dev/null +++ b/test/parser/max_keyword_hits.js @@ -0,0 +1,19 @@ +const hljs = require('../../build'); + +describe("max keyword hits", function() { + it("should count a keyword 7 times for relevance, no more", () => { + hljs.registerLanguage('test-language', (hljs) => { + return { + keywords: "bob suzy|2" + }; + }); + + let result = hljs.highlight('bob bob bob bob bob bob bob bob bob bob bob bob bob', { language: 'test-language' }); + result.relevance.should.equal(7); + + result = hljs.highlight('suzy suzy suzy suzy suzy suzy suzy suzy suzy suzy suzy suzy suzy', { language: 'test-language' }); + result.relevance.should.equal(14); + + hljs.unregisterLanguage("test-language"); + }); +}); diff --git a/test/parser/resume-scan.js b/test/parser/resume-scan.js new file mode 100644 index 0000000000..2e4d6c208a --- /dev/null +++ b/test/parser/resume-scan.js @@ -0,0 +1,26 @@ +'use strict'; + +const hljs = require('../../build'); +hljs.debugMode(); // tests run in debug mode so errors are raised + +describe("bugs", function() { + describe("resume scan when a match is ignored", () => { + it("should continue to highlight later matches", () => { + const result = hljs.highlight('ImmutablePair.of(Stuff.class, "bar")', {language: 'java'}); + result.value.should.equal( + 'ImmutablePair.of(Stuff.class, "bar")' + ); + }); + // previously the match rule was resumed but it would scan ahead too far and ignore + // later matches that matched the PRIOR rules... this is because when we "skip" (ignore) a + // rule we really only want to skip searching for THAT rule at that same location, we + // do not want to stop searching for ALL the prior rules at that location... + it("BUT should not skip ahead too far", () => { + const result = hljs.highlight('ImmutablePair.of(Stuff.class, "bar");\n23', {language: 'java'}); + result.value.should.equal( + 'ImmutablePair.of(Stuff.class, "bar");\n' + + '23' + ); + }); + }); +}); diff --git a/test/parser/reuse-endsWithParent.js b/test/parser/reuse-endsWithParent.js index 7a4d2fe9dc..d8b8f8e621 100644 --- a/test/parser/reuse-endsWithParent.js +++ b/test/parser/reuse-endsWithParent.js @@ -16,7 +16,7 @@ describe("bugs", function () { }; }); - hljs.highlight('test-language', '(abc 123) [abc 123] (abc 123)').value + hljs.highlight('(abc 123) [abc 123] (abc 123)', {language: 'test-language'}).value .should.equal( '(abc 123) ' + '[abc 123] ' + diff --git a/test/parser/should-not-destroyData.js b/test/parser/should-not-destroyData.js index 630e6af3b2..bdb6ffcd08 100644 --- a/test/parser/should-not-destroyData.js +++ b/test/parser/should-not-destroyData.js @@ -1,6 +1,6 @@ const hljs = require('../../build'); -describe("bugs", function () { +describe("parser/should not destroy data", function () { // CONTEXT: https://github.com/highlightjs/highlight.js/pull/2219 describe("a grammar with a mode that makes a 0 width match", () => { @@ -11,7 +11,7 @@ describe("bugs", function () { // broken regex from old Fortran ruleset const NUMBER = { className: "number", - begin: '(?=\\b|\\+|\\-|\\.)(?=\\.\\d|\\d)(?:\\d+)?(?:\\.?\\d*)(?:[de][+-]?\\d+)?\\b\\.?', + begin: '(?=\\b|\\+|-|\\.)(?=\\.\\d|\\d)(?:\\d+)?(?:\\.?\\d*)(?:[de][+-]?\\d+)?\\b\\.?', } return { @@ -19,21 +19,21 @@ describe("bugs", function () { }; }); - hljs.highlight('test-language', 'The number is 123_longint yes.').value + hljs.highlight('The number is 123_longint yes.', {language: 'test-language' }).value .should.equal( - // The whole number isn't highlighted (the rule doesn't handle the _type) - // But the key thing is the "1" is registered as a match for the rule - // instead of disappearing from the output completely, which is what - // would happen previously. - 'The number is 123_longint yes.' - // Incorrect prior output: - // 'The number is 23_longint yes.' - ) + // The whole number isn't highlighted (the rule doesn't handle the _type) + // But the key thing is the "1" is registered as a match for the rule + // instead of disappearing from the output completely, which is what + // would happen previously. + 'The number is 123_longint yes.' + // Incorrect prior output: + // 'The number is 23_longint yes.' + ); hljs.debugMode(); should(() => { - hljs.highlight('test-language', 'The number is 123_longint yes.').value + hljs.highlight('The number is 123_longint yes.', {language: 'test-language'}).value }).throw(Error, { - message: "0 width match regex", + message: /0 width match regex/, languageName: "test-language"}) }) }) diff --git a/test/regex/index.js b/test/regex/index.js new file mode 100644 index 0000000000..29c29f0d22 --- /dev/null +++ b/test/regex/index.js @@ -0,0 +1,407 @@ +'use strict'; + +const hljs = require('../../build'); +const { BFS, parseRegex, regexFor } = require('./lib/util.js'); +const { visitRegExpAST } = require('regexpp'); +const { JS, Words, NFA, CharSet } = require('refa'); +const { firstOf, underAStar, isFirstMatch, isAlwaysZeroWidth} = require('./lib/analysis.js'); + +hljs.debugMode(); + +/** + * A map for a regex pattern to whether or not it it vulnerable to exponential backtracking. + * + * @type {Record} + */ +const expBacktrackingCache = {}; + +/** + * A map for a regex pattern to whether or not it it vulnerable to polynomial backtracking. + * + * @type {Record} + */ +const polyBacktrackingCache = {}; + +function retrieveRules(language, { name }) { + // first we need to get the language compiled so we have + // access to the raw regex + hljs.highlight("", {language: name}); + return regexFor(language, { context: name, depth: 0 }); +} + +function forEachPattern(list, fn) { + const errors = []; + for (const rule of list) { + // console.log(rule) + const ast = parseRegex(rule.re); + fn({ + ast, + pattern: rule.re, + rulePath: rule.path, + reportError: message => errors.push(message) + }); + }; + if (errors.length > 0) { + throw new Error(errors.map(e => String(e.message || e)).join('\n\n')); + } +} + +function testLanguage(languageName) { + const language = hljs.getLanguage(languageName); + const rules = retrieveRules(language, { name: languageName }); + count += rules.length; + describe(languageName, function() { + it("have a name", function() { + language.name.should.not.equal(undefined); + }); + + // it('should not match the empty string', function () { + // forEachPattern(rules, ({ pattern, rulePath }) => { + // ''.should.not.match(pattern, `${rulePath}: ${pattern} should not match the empty string.\n\n` + + // `Patterns that do match the empty string can potentially cause infinitely many empty tokens. ` + + // `Make sure that all patterns always consume at least one character.`); + // }); + // }); + + it(`have ${rules.length} regex matchers`, () => {} ); + + it('should not use octal escapes', function() { + forEachPattern(rules, ({ ast, rulePath, reportError }) => { + visitRegExpAST(ast.pattern, { + onCharacterEnter(node) { + if (/^\\(?:[1-9]|\d{2,})$/.test(node.raw)) { + reportError(`${rulePath}: Octal escape ${node.raw}.\n\n` + + `Octal escapes can be confused with backreferences, so please do not use them.\n` + + `To fix this, use a different escape method. ` + + `Note that this could also be an invalid backreference, so be sure to carefully analyse the pattern.`); + } + } + }); + }); + }); + + it('should not cause exponential backtracking', function () { + forEachPattern(rules, ({ pattern, ast, rulePath, reportError }) => { + const patternStr = String(pattern); + if (expBacktrackingCache[patternStr] === false) { + // we know that the pattern won't cause exp backtracking because we checked before + return; + } + + const parser = JS.Parser.fromAst(ast); + /** + * Parses the given element and creates its NFA. + * + * @param {import("refa").JS.ParsableElement} element + * @returns {NFA} + */ + function toNFA(element, debug = false) { + const { expression, maxCharacter } = parser.parseElement(element, { + backreferences: "resolve", + lookarounds: "disable", + }); + return NFA.fromRegex(expression, { maxCharacter }); + } + + /** + * Checks whether the alternatives of the given node are disjoint. If the alternatives are not disjoint + * and the give node is a descendant of an effective Kleene star, then an error will be thrown. + * + * @param {CapturingGroup | Group | LookaroundAssertion} node + * @returns {void} + */ + function checkDisjointAlternatives(node) { + if (!underAStar(node) || node.alternatives.length < 2) { + return; + } + + const alternatives = node.alternatives; + + const total = toNFA(alternatives[0]); + total.removeEmptyWord(); + for (let i = 1, l = alternatives.length; i < l; i++) { + const a = alternatives[i]; + const current = toNFA(a); + current.removeEmptyWord(); + + if (!total.isDisjointWith(current)) { + reportError(`${rulePath}: The alternative \`${a.raw}\` is not disjoint with at least one previous alternative.` + + ` This will cause exponential backtracking.` + + `\n\nTo fix this issue, you have to rewrite the ${node.type} \`${node.raw}\`.` + + ` The goal is that all of its alternatives are disjoint.` + + ` This means that if a (sub-)string is matched by the ${node.type}, then only one of its alternatives can match the (sub-)string.` + + `\n\nExample: \`(?:[ab]|\\w|::)+\`` + + `\nThe alternatives of the group are not disjoint because the string "a" can be matched by both \`[ab]\` and \`\\w\`.` + + ` In this example, the pattern by easily fixed because the \`[ab]\` is a subset of the \`\\w\`, so its enough to remove the \`[ab]\` alternative to get \`(?:\\w|::)+\` as the fixed pattern.` + + `\nIn the real world, patterns can be a lot harder to fix.` + + ` If you are trying to make the tests pass for a pull request but can\'t fix the issue yourself, then make the pull request (or commit) anyway.` + + ` A maintainer will help you.` + + `\n\nFull pattern:\n${pattern}`); + } else if (i !== l - 1) { + total.union(current); + } + } + } + + visitRegExpAST(ast.pattern, { + onCapturingGroupLeave: checkDisjointAlternatives, + onGroupLeave: checkDisjointAlternatives, + onAssertionLeave(node) { + if (node.kind === "lookahead" || node.kind === "lookbehind") { + checkDisjointAlternatives(node); + } + }, + + onQuantifierLeave(node) { + if (node.max < 10) { + return; // not a star + } + if (node.element.type !== "CapturingGroup" && node.element.type !== "Group") { + return; // not a group + } + + // The idea here is the following: + // + // We have found a part `A*` of the regex (`A` is assumed to not accept the empty word). Let `I` be + // the intersection of `A` and `A{2,}`. If `I` is not empty, then there exists a non-empty word `w` + // that is accepted by both `A` and `A{2,}`. That means that there exists some `m>1` for which `w` + // is accepted by `A{m}`. + // This means that there are at least two ways `A*` can accept `w`. It can be accepted as `A` or as + // `A{m}`. Hence there are at least 2^n ways for `A*` to accept the word `w{n}`. This is the main + // requirement for exponential backtracking. + // + // This is actually only a crude approximation for the real analysis that would have to be done. We + // would actually have to check the intersection `A{p}` and `A{p+1,}` for all p>0. However, in most + // cases, the approximation is good enough. + + const nfa = toNFA(node.element, true); + nfa.removeEmptyWord(); + const twoStar = nfa.copy(); + twoStar.quantify(2, Infinity); + + if (!nfa.isDisjointWith(twoStar)) { + const example = Words.fromUnicodeToString(firstOf(NFA.intersectionWords(nfa, twoStar))); + + reportError(`${rulePath}: The quantifier \`${node.raw}\` ambiguous for all words ${JSON.stringify(example)}.repeat(n) for any n>1.` + + ` This will cause exponential backtracking.` + + `\n\nTo fix this issue, you have to rewrite the element (let's call it E) of the quantifier.` + + ` The goal is modify E such that it is disjoint with repetitions of itself.` + + ` This means that if a (sub-)string is matched by E, then it must not be possible for E{2}, E{3}, E{4}, etc. to match that (sub-)string.` + + `\n\nExample: \`(?:\\w+|::)+\`` + + `\nThe problem lies in \`\\w+\` because \`\\w+\` and \`(?:\\w+){2}\` are not disjoint as the string "aa" is fully matched by both.` + + ` In this example, the pattern by easily fixed by changing \`\\w+\` to \`\\w\`.` + + `\nIn the real world, patterns can be a lot harder to fix.` + + ` If you are trying to make the tests pass for a pull request but can\'t fix the issue yourself, then make the pull request (or commit) anyway.` + + ` A maintainer will help you.` + + `\n\nFull pattern:\n${pattern}`); + } + }, + }); + + expBacktrackingCache[patternStr] = false; + }); + }); + it('should not cause polynomial backtracking', function () { + forEachPattern(rules, ({ pattern, ast, rulePath, reportError }) => { + const patternStr = String(pattern); + if (polyBacktrackingCache[patternStr] === false) { + // we know that the pattern won't cause poly backtracking because we checked before + return; + } + + const EMPTY = ast.flags.unicode ? CharSet.empty(0x10FFFF) : CharSet.empty(0xFFFF); + + /** + * @param {Node} node + * @returns {CharSet} + */ + function toCharSet(node) { + switch (node.type) { + case "Alternative": { + if (node.elements.length === 1) { + return toCharSet(node.elements[0]); + } + return EMPTY; + } + case "CapturingGroup": + case "Group": { + let total = EMPTY; + for (const item of node.alternatives) { + total = total.union(toCharSet(item)); + } + return total; + } + case "Character": + return JS.createCharSet([node.value], ast.flags); + case "CharacterClass": { + const value = JS.createCharSet(node.elements.map(x => { + if (x.type === "CharacterSet") { + return x; + } else if (x.type === "Character") { + return x.value; + } else { + return { min: x.min.value, max: x.max.value }; + } + }), ast.flags); + if (node.negate) { + return value.negate(); + } else { + return value; + } + } + case "CharacterSet": + return JS.createCharSet([node], ast.flags); + + default: + return EMPTY; + } + } + + /** + * @param {Element} from + * @returns {Element | null} + */ + function getAfter(from) { + const parent = from.parent; + if (parent.type === "Quantifier") { + return getAfter(parent); + } else if (parent.type === "Alternative") { + const index = parent.elements.indexOf(from); + const after = parent.elements[index + 1]; + if (after) { + return after; + } else { + const grandParent = parent.parent; + if (grandParent.type === "Pattern") { + return null; + } else { + return getAfter(grandParent); + } + } + } else { + throw Error("Unreachable"); + } + } + + visitRegExpAST(ast.pattern, { + onQuantifierLeave(node) { + if (node.max !== Infinity) { + return; + } + const char = toCharSet(node.element); + tryReachUntil(getAfter(node), char, null); + + /** + * @param {Quantifier} quantifier + * @param {CharSet} char + */ + function assertNoPoly(quantifier, char) { + if (quantifier.max === Infinity) { + const qChar = toCharSet(quantifier.element); + if (qChar && !qChar.isDisjointWith(char)) { + const intersection = qChar.intersect(char); + const literal = JS.toLiteral({ + type: "Concatenation", + elements: [ + { type: "CharacterClass", characters: intersection } + ] + }) + const lang = `/${literal.source}/${literal.flags}`; + + const rangeStr = patternStr.substring(node.start + 1, quantifier.end + 1); + const rangeHighlight = `^${"~".repeat(node.end - node.start - 1)}${" ".repeat(quantifier.start - node.end)}^${"~".repeat(quantifier.end - quantifier.start - 1)}`; + + reportError(`${rulePath}: Polynomial backtracking. By repeating any character that matches ${lang}, an attack string can be created.\n\n ${rangeStr}\n ${rangeHighlight}\n\nFull pattern:\n${patternStr}\n${" ".repeat(node.start + 1)}${rangeHighlight}`); + } + } + } + + /** + * @param {Element | null | undefined} element + * @param {CharSet} char + * @param {Element | null | undefined} until + * @returns {CharSet} + */ + function tryReachUntil(element, char, until) { + if (!element || element == until || char.isEmpty) { + return char; + } + + const after = getAfter(element); + + if (element.type === "Quantifier") { + assertNoPoly(element, char); + } + + return tryReachUntil(after, goInto(element, after, char), until); + } + + /** + * @param {Element} element + * @param {Element} after + * @param {CharSet} char + * @returns {CharSet} + */ + function goInto(element, after, char) { + switch (element.type) { + case "Assertion": { + if (element.kind === "lookahead" || element.kind === "lookbehind") { + for (const alt of element.alternatives) { + if (alt.elements.length > 0) { + tryReachUntil(alt.elements[0], char, after); + } + } + } + return EMPTY; + } + case "Group": + case "CapturingGroup": { + let total = EMPTY; + for (const alt of element.alternatives) { + if (alt.elements.length > 0) { + total = total.union(tryReachUntil(alt.elements[0], char, after)); + } else { + total = char; + } + } + return total; + } + case "Character": + case "CharacterClass": + case "CharacterSet": { + return char.intersect(toCharSet(element)); + } + case "Quantifier": { + if (element.min === 0) { + goInto(element.element, after, char); + return char; + } else { + return goInto(element.element, after, char); + } + } + default: + return EMPTY; + } + } + }, + }); + + polyBacktrackingCache[patternStr] = false; + }); + }); + }); +} + +let count = 0; +let languages = hljs.listLanguages(); +if (process.env.ONLY_LANG) { + languages = [process.env.ONLY_LANG]; +} + +for (const language of languages) { + testLanguage(language); +} + +describe("COMBINED: All grammars", () => { + it(`have ${count} total regex`, () => {}); +}); diff --git a/test/regex/lib/analysis.js b/test/regex/lib/analysis.js new file mode 100644 index 0000000000..3b14fbcb4a --- /dev/null +++ b/test/regex/lib/analysis.js @@ -0,0 +1,87 @@ +/** + * Returns whether the given element will always have zero width meaning that it doesn't consume characters. + * + * @param {Element} element + * @returns {boolean} + */ +function isAlwaysZeroWidth(element) { + switch (element.type) { + case 'Assertion': + // assertions == ^, $, \b, lookarounds + return true; + case 'Quantifier': + return element.max === 0 || isAlwaysZeroWidth(element.element); + case 'CapturingGroup': + case 'Group': + // every element in every alternative has to be of zero length + return element.alternatives.every(alt => alt.elements.every(isAlwaysZeroWidth)); + case 'Backreference': + // on if the group referred to is of zero length + return isAlwaysZeroWidth(element.resolved); + default: + return false; // what's left are characters + } +} + +/** + * Returns whether the given element will always at the start of the whole match. + * + * @param {Element} element + * @returns {boolean} + */ +function isFirstMatch(element) { + const parent = element.parent; + switch (parent.type) { + case 'Alternative': + // all elements before this element have to of zero length + if (!parent.elements.slice(0, parent.elements.indexOf(element)).every(isAlwaysZeroWidth)) { + return false; + } + const grandParent = parent.parent; + if (grandParent.type === 'Pattern') { + return true; + } else { + return isFirstMatch(grandParent); + } + + case 'Quantifier': + if (parent.max >= 2) { + return false; + } else { + return isFirstMatch(parent); + } + + default: + throw new Error(`Internal error: The given node should not be a '${element.type}'.`); + } +} + +/** + * Returns whether the given node either is or is a child of what is effectively a Kleene star. + * + * @param {import("regexpp/ast").Node} node + * @returns {boolean} + */ +function underAStar(node) { + if (node.type === "Quantifier" && node.max > 10) { + return true; + } else if (node.parent) { + return underAStar(node.parent); + } else { + return false; + } +} + +/** + * @param {Iterable} iter + * @returns {T | undefined} + * @template T + */ +function firstOf(iter) { + for (const item of iter) { + return item; + } + return undefined; +} + +module.exports = { firstOf, underAStar, isFirstMatch, isAlwaysZeroWidth}; diff --git a/test/regex/lib/util.js b/test/regex/lib/util.js new file mode 100644 index 0000000000..f253d8328a --- /dev/null +++ b/test/regex/lib/util.js @@ -0,0 +1,109 @@ +/* eslint-disable no-undefined */ + +const { RegExpParser } = require('regexpp'); + +/** + * @typedef {import("regexpp/ast").Pattern} Pattern + * @typedef {import("regexpp/ast").Flags} Flags + * @typedef {{ pattern: Pattern, flags: Flags }} LiteralAST + */ + +const parser = new RegExpParser({ strict: false, ecmaVersion: 2018 }); +// ecmaVersion 2018 is ECMAScript 9 + +/** @type {Map} */ +const astCache = new Map(); + +// exclude our common "match anything" matchers +function matchAny(re) { + return re.source === "\\B|\\b"; +} + +function regexFor(mode, { context, depth }) { + if (mode.analyzed) return []; + mode.analyzed = true; + + let list = []; + if (mode.beginRe && !matchAny(mode.beginRe)) list.push({ path: `${context}/begin`, re: mode.beginRe }); + if (mode.endRe && !matchAny(mode.endRe)) list.push({ path: `${context}/end`, re: mode.endRe }); + if (mode.illegalRe) list.push({ path: `${context}/illegal`, re: mode.illegalRe }); + if (mode.keywordPatternRe && mode.keywordPatternRe.source !== "\\w+") { + list.push({ path: `${context}/$keyword_pattern`, re: mode.keywordPatternRe }); + } + if (mode.contains.length) { + mode.contains.forEach((mode, i) => { + const nodeName = `[${i}]${mode.className || ""}`; + const modes = regexFor(mode, { context: `${context}/${nodeName}`, depth: depth + 1 }); + list = [...list, ...modes]; + }); + } + if (mode.starts) { + const nodeName = "$starts"; + const modes = regexFor(mode.starts, { context: `${context}/${nodeName}`, depth: depth + 1 }); + list = [...list, ...modes]; + } + return list; +} + +/** + * Performs a breadth-first search on the given start element. + * + * @param {any} start + * @param {(path: { key: string, value: any }[]) => void} callback + */ +const BFS = (start, callback) => { + const visited = new Set(); + /** @type {{ key: string, value: any }[][]} */ + let toVisit = [ + [{ key: null, value: start }] + ]; + + callback(toVisit[0]); + + while (toVisit.length > 0) { + /** @type {{ key: string, value: any }[][]} */ + const newToVisit = []; + + for (const path of toVisit) { + const obj = path[path.length - 1].value; + if (!visited.has(obj)) { + visited.add(obj); + + for (const key in obj) { + const value = obj[key]; + + path.push({ key, value }); + callback(path); + + if (Array.isArray(value) || Object.prototype.toString.call(value) === '[object Object]') { + newToVisit.push([...path]); + } + + path.pop(); + } + } + } + + toVisit = newToVisit; + } +}; + +/** + * Returns the AST of a given pattern. + * + * @param {RegExp} regex + * @returns {LiteralAST} + */ +const parseRegex = (regex) => { + const key = regex.toString(); + let literal = astCache.get(key); + if (literal === undefined) { + const flags = parser.parseFlags(regex.flags, undefined); + const pattern = parser.parsePattern(regex.source, undefined, undefined, flags.unicode); + literal = { pattern, flags }; + astCache.set(key, literal); + } + return literal; +}; + +module.exports = { BFS, regexFor, parseRegex }; diff --git a/test/special/buildClassName.js b/test/special/buildClassName.js index 3b06170f09..ac287a6e37 100644 --- a/test/special/buildClassName.js +++ b/test/special/buildClassName.js @@ -1,37 +1,35 @@ 'use strict'; -const _ = require('lodash'); - describe('block class names', () => { before( () => { const testHTML = document.querySelectorAll('#build-classname .hljs'); - this.blocks = _.map(testHTML, 'className'); + this.blocks = [...testHTML].map((x) => x.className); }); it('should add language class name to block', () => { - const expected = 'some-class hljs xml', + const expected = 'some-class hljs language-xml', actual = this.blocks[0]; actual.should.equal(expected); }); it('should not clutter block class (first)', () => { - const expected = 'hljs some-class xml', + const expected = 'hljs some-class language-xml', actual = this.blocks[1]; actual.should.equal(expected); }); it('should not clutter block class (last)', () => { - const expected = 'some-class hljs xml', + const expected = 'some-class hljs language-xml', actual = this.blocks[2]; actual.should.equal(expected); }); it('should not clutter block class (spaces around)', () => { - const expected = 'hljs some-class xml', + const expected = 'hljs some-class language-xml', actual = this.blocks[3]; actual.should.equal(expected); diff --git a/test/special/customMarkup.js b/test/special/customMarkup.js deleted file mode 100644 index 5dda648c73..0000000000 --- a/test/special/customMarkup.js +++ /dev/null @@ -1,43 +0,0 @@ -'use strict'; - -const _ = require('lodash'); -const utility = require('../utility'); - -describe('custom markup', () => { - before(() => { - const testHTML = document.querySelectorAll('#custom-markup .hljs'); - - this.blocks = _.map(testHTML, 'innerHTML'); - }); - - it('should replace tabs', () => { - const filename = utility.buildPath('fixtures', 'expect', - 'tabreplace.txt'), - actual = this.blocks[0]; - - return utility.expectedFile(filename, 'utf-8', actual); - }); - - it('should keep custom markup', () => { - const filename = utility.buildPath('fixtures', 'expect', - 'custommarkup.txt'), - actual = this.blocks[1]; - - return utility.expectedFile(filename, 'utf-8', actual); - }); - - it('should keep custom markup and replace tabs', () => { - const filename = utility.buildPath('fixtures', 'expect', - 'customtabreplace.txt'), - actual = this.blocks[2]; - - return utility.expectedFile(filename, 'utf-8', actual); - }); - - it('should keep the same amount of void elements (
    ,
    , ...)', () => { - const filename = utility.buildPath('fixtures', 'expect', 'brInPre.txt'), - actual = this.blocks[3]; - - return utility.expectedFile(filename, 'utf-8', actual); - }); -}); diff --git a/test/special/index.js b/test/special/index.js index c930842080..9fd2881c33 100644 --- a/test/special/index.js +++ b/test/special/index.js @@ -1,7 +1,8 @@ 'use strict'; -const _ = require('lodash'); const hljs = require('../../build'); +hljs.debugMode(); // tests run in debug mode so errors are raised + const { JSDOM } = require('jsdom'); const { readFile } = require('fs').promises; const utility = require('../utility'); @@ -20,21 +21,20 @@ describe('special cases tests', () => { // Setup hljs environment hljs.configure({ tabReplace: ' ' }); - hljs.initHighlighting(); + let blocks = document.querySelectorAll('pre code'); + blocks.forEach(hljs.highlightElement); // Setup hljs for non-`
    ` tests
    -    hljs.configure({ useBR: true });
    +    hljs.configure();
     
    -    let blocks = document.querySelectorAll('.code');
    -    _.each(blocks, hljs.highlightBlock);
    +    blocks = document.querySelectorAll('.code');
    +    blocks.forEach(hljs.highlightElement);
       });
     
       require('./explicitLanguage');
    -  require('./customMarkup');
       require('./languageAlias');
       require('./noHighlight');
       require('./subLanguages');
       require('./buildClassName');
    -  require('./useBr');
       require('./endsWithParentVariants')
     });
    diff --git a/test/special/languageAlias.js b/test/special/languageAlias.js
    index 8dc9d71fe6..7f80294cc3 100644
    --- a/test/special/languageAlias.js
    +++ b/test/special/languageAlias.js
    @@ -1,13 +1,12 @@
     'use strict';
     
    -const _       = require('lodash');
     const utility = require('../utility');
     
     describe('language alias', () => {
       before(() => {
         const testHTML = document.querySelectorAll('#language-alias .hljs');
     
    -    this.blocks = _.map(testHTML, 'innerHTML');
    +    this.blocks = [...testHTML].map(x => x.innerHTML);
       });
     
       it('should highlight as aliased language', () => {
    diff --git a/test/special/noHighlight.js b/test/special/noHighlight.js
    index de085ddb47..3907b37722 100644
    --- a/test/special/noHighlight.js
    +++ b/test/special/noHighlight.js
    @@ -1,12 +1,10 @@
     'use strict';
     
    -const _ = require('lodash');
    -
     describe('no highlighting', () => {
       before(() => {
         const testHTML = document.querySelectorAll('#no-highlight pre');
     
    -    this.blocks   = _.map(testHTML, 'children[0].innerHTML');
    +    this.blocks   = [...testHTML].map((x) => x.children[0].innerHTML);
         this.expected = {
           html:   '<div id="contents">\n  ' +
                   '<p>Hello, World!\n</div>',
    diff --git a/test/special/useBr.js b/test/special/useBr.js
    deleted file mode 100644
    index 55b255fe9b..0000000000
    --- a/test/special/useBr.js
    +++ /dev/null
    @@ -1,30 +0,0 @@
    -'use strict';
    -
    -const utility = require('../utility');
    -
    -describe('use br', () => {
    -  before(() => {
    -    const filename = utility.buildPath('fixtures', 'expect', 'useBr.txt'),
    -          testHTML = document.querySelectorAll('#use-br .hljs');
    -
    -    return utility.setupFile(filename, 'utf-8', this, testHTML);
    -  });
    -
    -  it('should respect 
    tags', () => { - const actual = this.blocks[0]; - - actual.should.equal(this.expected); - }); - - it('should ignore literal new lines', () => { - const actual = this.blocks[1]; - - actual.should.equal(this.expected); - }); - - it('should recognize xml-style
    ', () => { - const actual = this.blocks[2]; - - actual.should.equal(this.expected); - }); -}); diff --git a/test/tools.js b/test/tools.js deleted file mode 100644 index 9c3ee1ad2a..0000000000 --- a/test/tools.js +++ /dev/null @@ -1,39 +0,0 @@ -'use strict'; - -const utility = require('../tools/utility'); -const path = require('path'); -const { readFile } = require('fs').promises; - -describe("minification tools", () => { - it("should replace API calls with minified names", () => { - let content = "hljs.COMMENT(); hj.NUMBER_MODE == 0; a = hljs.endRe"; - content.replace(utility.regex.replaces, utility.replaceClassNames).should.equal( - "hljs.C(); hj.NM == 0; a = hljs.eR" - ); - }); - - it("should replace API calls with minified names and protect declarations", () => { - let content = "hj.NUMBER_MODE == 0; hljs.COMMENT = 1; a = hljs.endRe"; - content.replace(utility.regex.replaces, utility.replaceClassNames).should.equal( - "hj.NM == 0; hljs.C = hljs.COMMENT = 1; a = hljs.eR" - ); - }); - - it("should NOT protect non-public member declarations", () => { - let content = "hljs.endRe = 3;"; - content.replace(utility.regex.replaces, utility.replaceClassNames).should.equal( - "hljs.eR = 3;" - ); - }); - - it("should assign API_REPLACES to the REPLACES dictionary in the highlight.js code", (done) => { - readFile(path.join(__dirname, "../src/highlight.js"), 'utf-8').then(function(content) { - "abc".should.containEql("bc"); - content.should.not.containEql("var API_REPLACES = " + JSON.stringify(utility.REPLACES)); - content.replace(utility.regex.apiReplacesFrom, utility.regex.apiReplacesTo) - .should - .containEql("var API_REPLACES = " + JSON.stringify(utility.REPLACES)); - done(); - }); - }); -}); diff --git a/test/utility.js b/test/utility.js index c656648227..60318df03f 100644 --- a/test/utility.js +++ b/test/utility.js @@ -1,19 +1,16 @@ 'use strict'; -const _ = require('lodash'); const { readFile } = require('fs').promises; const path = require('path'); // Build a path relative to `test/` exports.buildPath = function() { - const args = _.slice(arguments, 0), + const args = [...arguments], paths = [__dirname].concat(args); return path.join.apply(this, paths); }; -exports.numberToString = _.method('toString'); - exports.expectedFile = (filename, encoding, actual) => { return readFile(filename, encoding) .then(expected => actual.trim().should.equal(expected.trim())); @@ -23,6 +20,6 @@ exports.setupFile = (filename, encoding, that, testHTML) => { return readFile(filename, encoding) .then(expected => { that.expected = expected.trim(); - that.blocks = _.map(testHTML, 'innerHTML'); + that.blocks = [...testHTML].map(x => x.innerHTML); }); }; diff --git a/tools/build.js b/tools/build.js old mode 100644 new mode 100755 index 45ae780309..903976d5c2 --- a/tools/build.js +++ b/tools/build.js @@ -1,3 +1,4 @@ +#!/usr/bin/env node // For the basic introductions on using this build script, see: // // @@ -60,48 +61,50 @@ 'use strict'; const commander = require('commander'); -const path = require('path'); -const { clean } = require("./lib/makestuff") -const log = (...args) => console.log(...args) +const path = require('path'); +const { clean } = require("./lib/makestuff.js"); +const log = (...args) => console.log(...args); const TARGETS = ["cdn", "browser", "node"]; -let dir = {}; +const dir = {}; commander .usage('[options] [...]') .option('-n, --no-minify', 'Disable minification') - .option('-t, --target ', 'Build for target ' + - '[all, browser, cdn, node]', - 'browser') + .option('-ne, --no-esm', 'Disable building ESM') + .option('-t, --target ', + 'Build for target ' + + '[all, browser, cdn, node]', + 'browser') .parse(process.argv); -commander.target = commander.target.toLowerCase(); +const TARGET = commander.opts().target.toLowerCase(); -dir.root = path.dirname(__dirname); +dir.root = path.dirname(__dirname); dir.buildRoot = path.join(dir.root, 'build'); async function doTarget(target, buildDir) { - const build = require(`./build_${target}`); + const build = require(`./build_${target}`); process.env.BUILD_DIR = buildDir; await clean(buildDir); - await build.build({languages: commander.args, minify: commander.minify}); -}; + await build.build({ languages: commander.args, minify: commander.opts().minify, esm: commander.opts().esm }); +} async function doBuild() { - log ("Starting build."); - if (commander.target=="all") { + log("Starting build."); + if (TARGET === "all") { await clean(dir.buildRoot); - for (let target of TARGETS) { - log (`** Building ${target.toUpperCase()}. **`); - let buildDir = path.join(dir.buildRoot, target); + for (const target of TARGETS) { + log(`** Building ${target.toUpperCase()}. **`); + const buildDir = path.join(dir.buildRoot, target); await doTarget(target, buildDir); } - } else if (TARGETS.includes(commander.target)) { - doTarget(commander.target, dir.buildRoot); + } else if (TARGETS.includes(TARGET)) { + doTarget(TARGET, dir.buildRoot); } else { - log(`ERROR: I do not know how to build '${commander.target}'`); + log(`ERROR: I do not know how to build '${TARGET}'`); } - log ("Finished build."); + log("Finished build."); } -doBuild() +doBuild(); diff --git a/tools/build_browser.js b/tools/build_browser.js index 6f2a972a33..24ad4fbed8 100644 --- a/tools/build_browser.js +++ b/tools/build_browser.js @@ -1,63 +1,94 @@ -const _ = require('lodash'); +const _ = require('lodash'); const fs = require("fs").promises; +const fss = require("fs"); const glob = require("glob-promise"); const path = require("path"); const zlib = require('zlib'); const Terser = require("terser"); const child_process = require('child_process'); -const { getLanguages } = require("./lib/language"); -const { filter } = require("./lib/dependencies"); -const config = require("./build_config"); -const { install, install_cleancss, mkdir, renderTemplate } = require("./lib/makestuff"); +const { getLanguages } = require("./lib/language.js"); +const { filter } = require("./lib/dependencies.js"); +const config = require("./build_config.js"); +const { install, installCleanCSS, mkdir, renderTemplate } = require("./lib/makestuff.js"); const log = (...args) => console.log(...args); +const { rollupCode } = require("./lib/bundling.js"); +const bundling = require('./lib/bundling.js'); +const Table = require('cli-table'); + +const getDefaultHeader = () => ({ + ...require('../package.json'), + git_sha: child_process + .execSync("git rev-parse --short=10 HEAD") + .toString().trim() +}); +function buildHeader(args = getDefaultHeader()) { + return "/*!\n" + + ` Highlight.js v${args.version} (git: ${args.git_sha})\n` + + ` (c) ${config.copyrightYears} ${args.author.name} and other contributors\n` + + ` License: ${args.license}\n` + + ` */`; +} + +function sortByKey(array, key) { + return array.sort(function(a, b) { + const x = a[key]; + const y = b[key]; + return ((x < y) ? -1 : ((x > y) ? 1 : 0)); + }); +} -function buildHeader(args) { - return "/*\n" + - ` Highlight.js ${args.version} (${args.git_sha})\n` + - ` License: ${args.license}\n` + - ` Copyright (c) ${config.copyrightYears}, ${args.author.name}\n*/`; +function detailedGrammarSizes(languages) { + if (languages.length > 180) return; + + const resultTable = new Table({ + head: ['lang', 'minified'], + // colWidths: [20,20,10,20,10,20], + chars: { mid: '', 'left-mid': '', 'mid-mid': '', 'right-mid': '' }, + style: { + head: ['grey'] + } + }); + languages.map(async(lang) => { + resultTable.push([lang.name, lang.data.length]); + }); + console.log(resultTable.sort((b, a) => a[1] - b[1]).toString()); } async function buildBrowser(options) { - var languages = await getLanguages() + let languages = await getLanguages(); // filter languages for inclusion in the highlight.js bundle - languages = filter(languages, options["languages"]); + languages = filter(languages, options.languages); await installDocs(); - await installDemo(languages); + await installDemo(languages, { minify: options.minify }); - log("Preparing languages.") + log("Preparing languages."); await Promise.all( - languages.map(async (lang) => { - await lang.compile({terser: config.terser}); + languages.map(async(lang) => { + // await lang.compile({ terser: config.terser }); process.stdout.write("."); - } ) + }) ); log(""); - var size = await buildBrowserHighlightJS(languages, {minify: options.minify}) + detailedGrammarSizes(languages); - log("-----") - log("Core :", size.core ,"bytes"); - if (options.minify) - log("Core (min) :", size.core_min ,"bytes"); - log("Languages :", + const size = await buildCore("highlight", languages, { minify: options.minify, format: "cjs" }); + + log("-----"); + log("Languages (raw) :", languages.map((el) => el.data.length).reduce((acc, curr) => acc + curr, 0), "bytes"); + log("highlight.js :", size.fullSize, "bytes"); if (options.minify) { - log("Languages (min) :", - languages.map((el) => el.minified.length).reduce((acc, curr) => acc + curr, 0), "bytes"); - } - log("highlight.js :", size.full ,"bytes"); - if (options.minify) { - log("highlight.min.js :", size.minified ,"bytes"); - log("highlight.min.js.gz :", zlib.gzipSync(size.minifiedSrc).length ,"bytes"); + log("highlight.min.js :", size.minified, "bytes"); + log("highlight.min.js.gz :", zlib.gzipSync(size.minifiedSrc).length, "bytes"); } else { - log("highlight.js.gz :", zlib.gzipSync(size.fullSrc).length ,"bytes"); + log("highlight.js.gz :", zlib.gzipSync(size.fullSrc).length, "bytes"); } log("-----"); } -async function installDemo(languages) { +async function installDemo(languages, { minify }) { log("Writing demo files."); mkdir("demo"); installDemoStyles(); @@ -65,86 +96,158 @@ async function installDemo(languages) { const assets = await glob("./demo/*.{js,css}"); assets.forEach((file) => install(file)); - const css = await glob("styles/*.css", {cwd:"./src"}) - const styles = css.map((el) => ( - { "name": _.startCase(path.basename(el,".css")), "path": el } - )); - renderTemplate("./demo/index.html", "./demo/index.html", { styles , languages }); + renderIndex(languages, minify); +} + +async function renderIndex(languages, minify) { + languages = languages.filter((lang) => + // hide a few languages + lang.name !== "plaintext" + && lang.name !== "c-like" + // no sample means no demo + && lang.sample + ); + + languages.forEach((language) => { + if (!language.categories.length) { + language.categories.push("misc"); + } + language.categories.push("all"); + }); + + const categoryCounter = languages + .flatMap((language) => language.categories) + .reduce((map, category) => map.set(category, (map.get(category) || 0) + 1), new Map()); + const categories = [ + "common", + ...Array.from(categoryCounter.keys()) + .filter((category) => !["common", "misc", "all"].includes(category)) + .sort(), + "misc", + "all" + ] + .filter((category) => categoryCounter.has(category)) + .map((category) => ({ + category, + count: categoryCounter.get(category) + })); + + function nameForStyle(file) { + let name = _.startCase(path.basename(file, ".css")); + if (file.includes("base16/")) { + name = `Base16 / ${name}`; + } + return name; + } + + const css = await glob("styles/**/*.css", { cwd: "./src" }); + let styles = css + .map((el) => ({ name: nameForStyle(el), path: el })) + .filter((style) => style.name !== "Default"); + styles = sortByKey(styles, "name"); + + renderTemplate("./demo/index.html", "./demo/index.html", { + categories, + languages, + minify, + styles + }); } async function installDocs() { log("Writing docs files."); mkdir("docs"); - let docs = await glob("./docs/*.rst"); + const docs = await glob("./docs/*.rst"); docs.forEach((file) => install(file)); } function installDemoStyles() { log("Writing style files."); mkdir("demo/styles"); - - glob.sync("*", {cwd: "./src/styles"}).forEach((file) => { - if (file.endsWith(".css")) - install_cleancss(`./src/styles/${file}`,`demo/styles/${file}`); - else // images, backgrounds, etc - install(`./src/styles/${file}`,`demo/styles/${file}`); - }) + mkdir("demo/styles/base16"); + + glob.sync("**", { cwd: "./src/styles" }).forEach((file) => { + const stat = fss.statSync(`./src/styles/${file}`); + if (file.endsWith(".css")) { + installCleanCSS(`./src/styles/${file}`, `demo/styles/${file}`); + } else if (!stat.isDirectory) { + // images, backgrounds, etc + install(`./src/styles/${file}`, `demo/styles/${file}`); + } + }); } -async function buildBrowserHighlightJS(languages, {minify}) { - log("Building highlight.js."); - - var git_sha = child_process - .execSync("git rev-parse HEAD") - .toString().trim() - .slice(0,8) - var versionDetails = {...require("../package"), git_sha}; - var header = buildHeader(versionDetails); - - var outFile = `${process.env.BUILD_DIR}/highlight.js`; - var minifiedFile = outFile.replace(/js$/,"min.js"); - var librarySrc = await fs.readFile("src/highlight.js", {encoding: "utf8"}); - var coreSize = librarySrc.length; - - // strip off the original top comment - librarySrc = librarySrc.replace(/\/\*.*?\*\//s,""); - - var workerStub = "if (typeof importScripts === 'function') { var hljs = self.hljs; }"; - - var fullSrc = [ - header, librarySrc, workerStub, - ...languages.map((lang) => lang.module) ].join("\n"); - - var tasks = []; - tasks.push(fs.writeFile(outFile, fullSrc, {encoding: "utf8"})); - - var core_min = []; - var minifiedSrc = ""; - - if (minify) { - var tersed = Terser.minify(librarySrc, config.terser) +const builtInLanguagesPlugin = (languages) => ({ + name: "hljs-index", + resolveId(source) { + if (source === "builtInLanguages") { + return source; // this signals that rollup should not ask other plugins or check the file system to find this id + } + return null; // other ids should be handled as usually + }, + load(id) { + const escape = (s) => "grmr_" + s.replace("-", "_"); + if (id === "builtInLanguages") { + return languages.map((lang) => + `export { default as ${escape(lang.name)} } from ${JSON.stringify(lang.path)};` + ).join("\n"); + } + return null; + } +}); + +async function buildCore(name, languages, options) { + const header = buildHeader(); + let relativePath = ""; + const input = { + ...config.rollup.core.input, + input: `src/stub.js` + }; + input.plugins = [ + ...input.plugins, + builtInLanguagesPlugin(languages) + ]; + const output = { + ...(options.format === "es" ? config.rollup.node.output : config.rollup.browser_iife.output), + file: `${process.env.BUILD_DIR}/${name}.js` + }; + + // optimize for no languages by not including the language loading stub + if (languages.length === 0) { + input.input = "src/highlight.js"; + } - minifiedSrc = [ - header, tersed.code, workerStub, - ...languages.map((lang) => lang.minified) ].join("\n"); + if (options.format === "es") { + output.format = "es"; + output.file = `${process.env.BUILD_DIR}/es/${name}.js`; + relativePath = "es/"; + } - // get approximate core minified size - core_min = [ header, tersed.code, workerStub].join().length; + log(`Building ${relativePath}${name}.js.`); - tasks.push(fs.writeFile(minifiedFile, minifiedSrc, {encoding: "utf8"})); + const index = await rollupCode(input, output); + const sizeInfo = { shas: [] }; + const writePromises = []; + if (options.minify) { + const { code } = await Terser.minify(index, { ...config.terser, module: (options.format === "es") }); + const src = `${header}\n${code}`; + writePromises.push(fs.writeFile(output.file.replace(/js$/, "min.js"), src)); + sizeInfo.minified = src.length; + sizeInfo.minifiedSrc = src; + sizeInfo.shas[`${relativePath}${name}.min.js`] = bundling.sha384(src); } - - await Promise.all(tasks); - return { - core: coreSize, - core_min: core_min, - minified: Buffer.byteLength(minifiedSrc, 'utf8'), - minifiedSrc, - fullSrc, - full: Buffer.byteLength(fullSrc, 'utf8') }; + { + const src = `${header}\n${index}`; + writePromises.push(fs.writeFile(output.file, src)); + sizeInfo.fullSize = src.length; + sizeInfo.fullSrc = src; + sizeInfo.shas[`${relativePath}${name}.js`] = bundling.sha384(src); + } + await Promise.all(writePromises); + return sizeInfo; } // CDN build uses the exact same highlight.js distributable -module.exports.buildBrowserHighlightJS = buildBrowserHighlightJS; +module.exports.buildCore = buildCore; module.exports.build = buildBrowser; diff --git a/tools/build_cdn.js b/tools/build_cdn.js index f1de6a0b1d..5ca6dc927b 100644 --- a/tools/build_cdn.js +++ b/tools/build_cdn.js @@ -1,79 +1,132 @@ const fs = require("fs").promises; +const fss = require("fs"); const glob = require("glob"); const zlib = require('zlib'); -const { getLanguages } = require("./lib/language"); -const { filter } = require("./lib/dependencies"); -const config = require("./build_config"); -const { install, install_cleancss, mkdir } = require("./lib/makestuff"); +const { getLanguages } = require("./lib/language.js"); +const { filter } = require("./lib/dependencies.js"); +const config = require("./build_config.js"); +const { install, installCleanCSS, mkdir } = require("./lib/makestuff.js"); const log = (...args) => console.log(...args); -const { buildBrowserHighlightJS } = require("./build_browser"); -const { buildPackageJSON } = require("./build_node"); +const { buildCore } = require("./build_browser.js"); +const { buildPackageJSON, writePackageJSON } = require("./build_node.js"); const path = require("path"); - -async function installPackageJSON() { - await buildPackageJSON(); - let json = require(`${process.env.BUILD_DIR}/package`); - json.name = "highlight.js-cdn-assets"; - fs.writeFile(`${process.env.BUILD_DIR}/package.json`, JSON.stringify(json, null, ' ')); +const bundling = require('./lib/bundling.js'); + +async function installPackageJSON(options) { + const json = buildPackageJSON(options); + json.name = "@highlightjs/cdn-assets"; + json.description = json.description.concat(" (pre-compiled CDN assets)"); + // this is not a replacement for `highlightjs` package + // CDN assets do not need an export map, they are just a bunch of files. + // The NPM package mostly only exists to populate CDNs and provide raw files. + delete json.exports; + delete json.type; + delete json.main; + delete json.types; + await writePackageJSON(json); } +let shas = {}; + async function buildCDN(options) { install("./LICENSE", "LICENSE"); - install("./README.CDN.md","README.md"); - installPackageJSON(); + install("./README.CDN.md", "README.md"); + await installPackageJSON(options); installStyles(); // all the languages are built for the CDN and placed into `/languages` const languages = await getLanguages(); - await installLanguages(languages); + + let esmCoreSize = {}; + let esmCommonSize = {}; + + await installLanguages(languages, options); // filter languages for inclusion in the highlight.js bundle - let embedLanguages = filter(languages, options["languages"]) + let embedLanguages = filter(languages, options.languages); // it really makes no sense to embed ALL languages with the CDN build, it's // more likely we want to embed NONE and have completely separate run-time // loading of some sort - if (embedLanguages.length == languages.length) { - embedLanguages = [] + if (embedLanguages.length === languages.length) { + embedLanguages = []; } - var size = await buildBrowserHighlightJS(embedLanguages, {minify: options.minify}) + const size = await buildCore("highlight", embedLanguages, { minify: options.minify, format: "cjs" }); + if (options.esm) { + mkdir("es"); + await fs.writeFile(`${process.env.BUILD_DIR}/es/package.json`, `{ "type": "module" }`); + esmCoreSize = await buildCore("core", [], { minify: options.minify, format: "es" }); + esmCommonSize = await buildCore("highlight", embedLanguages, { minify: options.minify, format: "es" }); + } + shas = { + ...size.shas, ...esmCommonSize.shas, ...esmCoreSize.shas, ...shas + }; + + await buildSRIDigests(shas); - log("-----") - log("Embedded Lang :", + log("-----"); + log("Embedded Lang :", embedLanguages.map((el) => el.minified.length).reduce((acc, curr) => acc + curr, 0), "bytes"); - log("All Lang :", + log("All Lang :", languages.map((el) => el.minified.length).reduce((acc, curr) => acc + curr, 0), "bytes"); - log("highlight.js :", - size.full, "bytes"); + log("highlight.js :", + size.fullSize, "bytes"); if (options.minify) { - log("highlight.min.js :", size.minified ,"bytes"); - log("highlight.min.js.gz :", zlib.gzipSync(size.minifiedSrc).length ,"bytes"); + log("highlight.min.js :", size.minified, "bytes"); + log("highlight.min.js.gz :", zlib.gzipSync(size.minifiedSrc).length, "bytes"); } else { - log("highlight.js.gz :", zlib.gzipSync(size.fullSrc).length ,"bytes"); + log("highlight.js.gz :", zlib.gzipSync(size.fullSrc).length, "bytes"); + } + if (options.esm) { + log("es/core.js :", esmCoreSize.fullSize, "bytes"); + log("es/highlight.js :", esmCommonSize.fullSize, "bytes"); + if (options.minify) { + log("es/core.min.js :", esmCoreSize.minified, "bytes"); + log("es/core.min.js.gz :", zlib.gzipSync(esmCoreSize.minifiedSrc).length, "bytes"); + log("es/highlight.min.js :", esmCommonSize.minified, "bytes"); + log("es/highlight.min.js.gz :", zlib.gzipSync(esmCommonSize.minifiedSrc).length, "bytes"); + } else { + log("es/highlight.js.gz :", zlib.gzipSync(esmCommonSize.fullSrc).length, "bytes"); + } } log("-----"); } -async function installLanguages(languages) { + +async function buildSRIDigests(shas) { + const temp = await fs.readFile("./tools/templates/DIGESTS.md"); + const DIGEST_MD = temp.toString(); + + const version = require("../package.json").version; + const digestList = Object.entries(shas).map(([k, v]) => `${v} ${k}`).join("\n"); + + const out = DIGEST_MD + .replace("", digestList) + .replace("", shas["highlight.min.js"]) + .replace("", shas["languages/go.min.js"]) + .replace(//g, version); + fs.writeFile(`${process.env.BUILD_DIR}/DIGESTS.md`, out); +} + +async function installLanguages(languages, options) { log("Building language files."); mkdir("languages"); + if (options.esm) mkdir("es/languages"); await Promise.all( - languages.map(async (language) => { - await buildCDNLanguage(language); + languages.map(async(language) => { + await buildCDNLanguage(language, options); process.stdout.write("."); - }) + }) ); log(""); await Promise.all( languages.filter((l) => l.third_party) - .map(async (language) => { - await buildDistributable(language); - }) + .map(async(lang) => await buildDistributable(lang, options)) ); log(""); @@ -81,31 +134,43 @@ async function installLanguages(languages) { function installStyles() { log("Writing style files."); - mkdir("styles"); - - glob.sync("*", {cwd: "./src/styles"}).forEach((file) => { - if (file.endsWith(".css")) - install_cleancss(`./src/styles/${file}`,`styles/${file.replace(".css",".min.css")}`); - else // images, backgrounds, etc - install(`./src/styles/${file}`,`styles/${file}`); - }) + mkdir("styles/base16"); + + glob.sync("**", { cwd: "./src/styles" }).forEach((file) => { + const stat = fss.statSync(`./src/styles/${file}`); + if (stat.isDirectory()) return; + + if (file.endsWith(".css")) { + installCleanCSS(`./src/styles/${file}`, `styles/${file.replace(".css", ".min.css")}`); + } else { + // images, backgrounds, etc + install(`./src/styles/${file}`, `styles/${file}`); + } + }); } -async function buildDistributable(language) { +async function buildDistributable(language, options) { const filename = `${language.name}.min.js`; - let distDir = path.join(language.moduleDir,"dist") - log(`Building ${distDir}/${filename}.`) - await fs.mkdir(distDir, {recursive: true}); - fs.writeFile(path.join(language.moduleDir,"dist",filename), language.minified); - + const distDir = path.join(language.moduleDir, "dist"); + log(`Building ${distDir}/${filename}.`); + await fs.mkdir(distDir, { recursive: true }); + await fs.writeFile(path.join(language.moduleDir, "dist", filename), language.minified); + if (options.esm) { + await fs.writeFile(path.join(language.moduleDir, "dist", filename.replace(".min.js", ".es.min.js")), language.minifiedESM); + } } - async function buildCDNLanguage (language) { - const filename = `${process.env.BUILD_DIR}/languages/${language.name}.min.js`; +async function buildCDNLanguage(language, options) { + const name = `languages/${language.name}.min.js`; - await language.compile({terser: config.terser}); - fs.writeFile(filename, language.minified); + await language.compile({ terser: config.terser }); + shas[name] = bundling.sha384(language.minified); + await fs.writeFile(`${process.env.BUILD_DIR}/${name}`, language.minified); + if (options.esm) { + shas[`es/${name}`] = bundling.sha384(language.minifiedESM); + await fs.writeFile(`${process.env.BUILD_DIR}/es/${name}`, language.minifiedESM); + } } module.exports.build = buildCDN; diff --git a/tools/build_config.js b/tools/build_config.js index 24dcdc8c2b..02f91373ad 100644 --- a/tools/build_config.js +++ b/tools/build_config.js @@ -1,49 +1,69 @@ -const cjsPlugin = require('rollup-plugin-commonjs'); +const cjsPlugin = require('@rollup/plugin-commonjs'); +const jsonPlugin = require('@rollup/plugin-json'); +const { nodeResolve } = require('@rollup/plugin-node-resolve'); module.exports = { - build_dir: "build", - copyrightYears: "2006-2020", - clean_css: {}, - rollup: { - node: { - output: { format: "cjs", strict: false }, - input : { - plugins: [ - cjsPlugin(), - { - transform: (x) => { - if (/var module/.exec(x)) { - // remove shim that only breaks things for rollup - return x.replace(/var module\s*=.*$/m,"") - } + build_dir: "build", + copyrightYears: `2006-${new Date().getFullYear()}`, + clean_css: { + level: 2 + }, + rollup: { + core: { + input: { + plugins: [ + cjsPlugin(), + jsonPlugin(), + nodeResolve(), + // TODO: remove with version 12 + { + transform: (x) => { + if (/var module/.exec(x)) { + // remove shim that only breaks things for rollup + return x.replace(/var module\s*=.*$/m, ""); } } - ], - }, + } + ] + } + }, + node: { + output: { + format: "cjs", + strict: false, + exports: "auto", + footer: "" + } + }, + browser_iife: { + input: { + plugins: [ + jsonPlugin(), + cjsPlugin(), + nodeResolve() + ] }, - browser: { - input: { - plugins: [ - cjsPlugin() - ] - }, - output: { - format: "iife", - outro: "return module.exports.definer || module.exports;", - strict: false, - compact: false, - interop: false, - extend: false, - } + output: { + name: "hljs", + format: "iife", + footer: "if (typeof exports === 'object' && typeof module !== 'undefined') { module.exports = hljs; }", + interop: false } + } + }, + terser: { + format: { + max_line_len: 80, + ascii_only: true }, - terser: { - "compress": { - passes: 2, - unsafe: true, - warnings: true, - dead_code: true, - toplevel: "funcs" - } + compress: { + ecma: 2015, + unsafe_arrows: true, + passes: 2, + unsafe: true, + warnings: true, + dead_code: true, + toplevel: "funcs" } -} + } +}; diff --git a/tools/build_node.js b/tools/build_node.js index 14da56d6fc..d22c2b4e05 100644 --- a/tools/build_node.js +++ b/tools/build_node.js @@ -1,103 +1,205 @@ const fs = require("fs").promises; -const config = require("./build_config"); -const { getLanguages } = require("./lib/language"); -const { install, mkdir } = require("./lib/makestuff"); -const { filter } = require("./lib/dependencies"); +const fss = require("fs"); +const config = require("./build_config.js"); +const glob = require("glob-promise"); +const { getLanguages } = require("./lib/language.js"); +const { install, mkdir, installCleanCSS } = require("./lib/makestuff.js"); +const { filter } = require("./lib/dependencies.js"); const { rollupWrite } = require("./lib/bundling.js"); const log = (...args) => console.log(...args); -async function buildNodeIndex(languages) { +// https://nodejs.org/api/packages.html#packages_writing_dual_packages_while_avoiding_or_minimizing_hazards +async function buildESMStub(name) { + const code = + `// https://nodejs.org/api/packages.html#packages_writing_dual_packages_while_avoiding_or_minimizing_hazards\n` + + `import HighlightJS from '../lib/${name}.js';\n` + + `export { HighlightJS };\n` + + `export default HighlightJS;\n`; + await fs.writeFile(`${process.env.BUILD_DIR}/es/${name}.js`, code); +} + +async function buildCJSIndex(name, languages) { const header = "var hljs = require('./core');"; - const footer = "module.exports = hljs;"; + const footer = + `hljs.HighlightJS = hljs\n` + + `hljs.default = hljs\n` + + `module.exports = hljs;`; const registration = languages.map((lang) => { - let require = `require('./languages/${lang.name}')`; - if (lang.loader) { - require = require += `.${lang.loader}`; - } + const require = `require('./languages/${lang.name}')`; return `hljs.registerLanguage('${lang.name}', ${require});`; - }) - - // legacy - await fs.writeFile(`${process.env.BUILD_DIR}/lib/highlight.js`, - "// This file has been deprecated in favor of core.js\n" + - "var hljs = require('./core');\n" - ) + }); const index = `${header}\n\n${registration.join("\n")}\n\n${footer}`; - await fs.writeFile(`${process.env.BUILD_DIR}/lib/index.js`, index); + await fs.writeFile(`${process.env.BUILD_DIR}/lib/${name}.js`, index); } - async function buildNodeLanguage (language) { - const input = { ...config.rollup.node.input, input: language.path } - const output = { ...config.rollup.node.output, file: `${process.env.BUILD_DIR}/lib/languages/${language.name}.js` } - await rollupWrite(input, output) +async function buildNodeLanguage(language, options) { + const EMIT = `function emitWarning() { + if (!emitWarning.warned) { + emitWarning.warned = true; + console.log( + 'Deprecation (warning): Using file extension in specifier is deprecated, use "highlight.js/lib/languages/%%%%" instead of "highlight.js/lib/languages/%%%%.js"' + ); + } + } + emitWarning();`; + const CJS_STUB = `${EMIT} + module.exports = require('./%%%%.js');`; + const ES_STUB = `${EMIT} + import lang from './%%%%.js'; + export default lang;`; + const input = { ...config.rollup.core.input, input: language.path }; + const output = { ...config.rollup.node.output, file: `${process.env.BUILD_DIR}/lib/languages/${language.name}.js` }; + await rollupWrite(input, output); + await fs.writeFile(`${process.env.BUILD_DIR}/lib/languages/${language.name}.js.js`, + CJS_STUB.replace(/%%%%/g, language.name)); + if (options.esm) { + await fs.writeFile(`${process.env.BUILD_DIR}/es/languages/${language.name}.js.js`, + ES_STUB.replace(/%%%%/g, language.name)); + await rollupWrite(input, { + ...output, + format: "es", + file: output.file.replace("/lib/", "/es/") + }); + } } -async function buildNodeHighlightJS() { - const input = { input: `src/highlight.js` } - const output = { ...config.rollup.node.output, file: `${process.env.BUILD_DIR}/lib/core.js` } - await rollupWrite(input, output) +const EXCLUDE = ["join"]; + +async function buildESMUtils() { + const input = { ...config.rollup.core.input, input: `src/lib/regex.js` }; + input.plugins = [...input.plugins, { + transform: (code) => { + EXCLUDE.forEach((fn) => { + code = code.replace(`export function ${fn}(`, `function ${fn}(`); + }); + return code; + } + }]; + await rollupWrite(input, { + ...config.rollup.node.output, + format: "es", + file: `${process.env.BUILD_DIR}/es/utils/regex.js` + }); } -async function buildPackageJSON() { - const CONTRIBUTOR = /^- (.*) <(.*)>$/ +async function buildNodeHighlightJS(options) { + const input = { ...config.rollup.core.input, input: `src/highlight.js` }; + const output = { ...config.rollup.node.output, file: `${process.env.BUILD_DIR}/lib/core.js` }; + output.footer = "highlight.HighlightJS = highlight;\nhighlight.default = highlight;"; + await rollupWrite(input, output); + if (options.esm) { + buildESMStub("core"); + } +} - let authors = await fs.readFile("AUTHORS.txt", {encoding: "utf8"}) - let lines = authors.split(/\r?\n/) - let json = require("../package") - json.contributors = lines.reduce((acc, line) => { - let matches = line.match(CONTRIBUTOR) +function dual(file) { + return { + require: file, + import: file.replace("/lib/", "/es/") + }; +} - if (matches) { - acc.push({ - name: matches[1], - email: matches[2] - }) - } - return acc; - }, []); - await fs.writeFile(`${process.env.BUILD_DIR}/package.json`, JSON.stringify(json, null, ' ')); +const generatePackageExports = () => ({ + ".": dual("./lib/index.js"), + "./package.json": "./package.json", + "./lib/common": dual("./lib/common.js"), + "./lib/core": dual("./lib/core.js"), + "./lib/languages/*": dual("./lib/languages/*.js"), + "./scss/*": "./scss/*", + "./styles/*": "./styles/*", + "./types/*": "./types/*" +}); +function buildPackageJSON(options) { + const packageJson = require("../package.json"); + + if (options.esm) packageJson.exports = generatePackageExports(); + + return packageJson; +} +function writePackageJSON(packageJson) { + return fs.writeFile(`${process.env.BUILD_DIR}/package.json`, JSON.stringify(packageJson, null, 2)); } -async function buildLanguages(languages) { +async function buildLanguages(languages, options) { log("Writing languages."); await Promise.all( - languages.map(async (lang) => { - await buildNodeLanguage(lang); + languages.map(async(lang) => { + await buildNodeLanguage(lang, options); process.stdout.write("."); }) - ) + ); log(""); } +const CORE_FILES = [ + "LICENSE", + "README.md", + "VERSION_10_UPGRADE.md", + "VERSION_11_UPGRADE.md", + "SUPPORTED_LANGUAGES.md", + "SECURITY.md", + "CHANGES.md", + "types/index.d.ts" +]; + async function buildNode(options) { mkdir("lib/languages"); - mkdir("scss"); - mkdir("styles"); + mkdir("scss/base16"); + mkdir("styles/base16"); + mkdir("types"); + - install("./LICENSE", "LICENSE"); - install("./README.md","README.md"); + CORE_FILES.forEach(file => { + install(`./${file}`, file); + }); + install("./src/core.d.ts", "lib/core.d.ts"); + install("./src/core.d.ts", "lib/common.d.ts"); + + if (options.esm) { + mkdir("es/languages"); + install("./src/core.d.ts", "es/core.d.ts"); + install("./src/core.d.ts", "es/common.d.ts"); + } log("Writing styles."); - const styles = await fs.readdir("./src/styles/"); - styles.forEach((file) => { - install(`./src/styles/${file}`,`styles/${file}`); - install(`./src/styles/${file}`,`scss/${file.replace(".css",".scss")}`); - }) - log("Writing package.json."); - await buildPackageJSON(); + // const styles = await fs.readdir("./src/styles/"); + glob.sync("**", { cwd: "./src/styles" }).forEach((file) => { + const stat = fss.statSync(`./src/styles/${file}`); + if (stat.isDirectory()) return; + + if (file.endsWith(".css")) { + installCleanCSS(`./src/styles/${file}`, `styles/${file}`); + installCleanCSS(`./src/styles/${file}`, `scss/${file.replace(".css", ".scss")}`); + } else { + // images, etc. + install(`./src/styles/${file}`, `styles/${file}`); + } + }); - let languages = await getLanguages() + let languages = await getLanguages(); // filter languages for inclusion in the highlight.js bundle - languages = filter(languages, options["languages"]); + languages = filter(languages, options.languages); + const common = languages.filter(l => l.categories.includes("common")); - await buildNodeIndex(languages); - await buildLanguages(languages); + log("Writing package.json."); + await writePackageJSON(buildPackageJSON(options)); + + if (options.esm) { + await fs.writeFile(`${process.env.BUILD_DIR}/es/package.json`, `{ "type": "module" }`); + await buildESMStub("index"); + await buildESMStub("common"); + await buildESMUtils(); + } + await buildCJSIndex("index", languages); + await buildCJSIndex("common", common); + await buildLanguages(languages, options); log("Writing highlight.js"); - await buildNodeHighlightJS(); + await buildNodeHighlightJS(options); } module.exports.build = buildNode; module.exports.buildPackageJSON = buildPackageJSON; +module.exports.writePackageJSON = writePackageJSON; diff --git a/tools/checkAutoDetect.js b/tools/checkAutoDetect.js old mode 100644 new mode 100755 index edefd40be6..0da66c8dfd --- a/tools/checkAutoDetect.js +++ b/tools/checkAutoDetect.js @@ -1,15 +1,16 @@ +#!/usr/bin/env node 'use strict'; -let fs = require('fs') -let hljs = require('../build'); -let path = require('path'); -let utility = require('../test/utility'); -let Table = require('cli-table'); -let colors = require('colors/safe'); +const fs = require('fs'); +const hljs = require('../build/lib/index.js'); +const path = require('path'); +const utility = require('../test/utility.js'); +const Table = require('cli-table'); +const colors = require('colors/safe.js'); -let resultTable = new Table({ - head: ['expected', 'actual', 'score', '2nd best', 'score'], - colWidths: [20,20,10,20, 10], +const resultTable = new Table({ + head: ['expected', 'actual', 'score', '2nd best', 'score', 'info'], + colWidths: [20, 20, 10, 20, 10, 20], style: { head: ['grey'] } @@ -23,15 +24,15 @@ function testAutoDetection(language, index, languages) { return fs.readFileSync(filename, 'utf-8'); }) .forEach(function(content) { - const expected = language, - actual = hljs.highlightAuto(content); - if (actual.language !== expected && actual.second_best.language !== expected) { + const expected = language; + const actual = hljs.highlightAuto(content); + if (actual.language !== expected && actual.secondBest.language !== expected) { return resultTable.push([ expected, colors.red(actual.language), actual.relevance ? actual.relevance : colors.grey('None'), - colors.red(actual.second_best.language), - actual.second_best.relevance ? actual.second_best.relevance : colors.grey('None') + colors.red(actual.secondBest.language), + actual.secondBest.relevance ? actual.secondBest.relevance : colors.grey('None') ]); } if (actual.language !== expected) { @@ -39,31 +40,44 @@ function testAutoDetection(language, index, languages) { expected, colors.yellow(actual.language), actual.relevance ? actual.relevance : colors.grey('None'), - colors.yellow(actual.second_best.language), - actual.second_best.relevance ? actual.second_best.relevance : colors.grey('None') + colors.yellow(actual.secondBest.language), + actual.secondBest.relevance ? actual.secondBest.relevance : colors.grey('None') + ]); + } + // equal relevance is flagged + if (actual.relevance === actual.secondBest.relevance) { + return resultTable.push([ + expected, + actual.language, + actual.relevance ? colors.yellow(actual.relevance) : colors.grey('None'), + actual.secondBest.language, + actual.secondBest.relevance ? colors.yellow(actual.secondBest.relevance) : colors.grey('None'), + "Relevance match." ]); } }); } -const languages = hljs.listLanguages() - .filter(hljs.autoDetection); +let languages = null; +if (process.env.ONLY_LANGUAGES) { + languages = process.env.ONLY_LANGUAGES.split(" "); +} else { + languages = hljs.listLanguages().filter(hljs.autoDetection); +} console.log('Checking auto-highlighting for ' + colors.grey(languages.length) + ' languages...'); languages.forEach((lang, index) => { - if (index%60===0) { console.log("") } - testAutoDetection(lang) + if (index % 60 === 0) { console.log(""); } + testAutoDetection(lang); process.stdout.write("."); }); -console.log("\n") +console.log("\n"); if (resultTable.length === 0) { - console.log(colors.green('SUCCESS') + ' - ' + colors.green(languages.length) + ' of ' + colors.gray(languages.length) + ' languages passed auto-highlight check!') + console.log(colors.green('SUCCESS') + ' - ' + colors.green(languages.length) + ' of ' + colors.gray(languages.length) + ' languages passed auto-highlight check!'); } else { console.log( - colors.red('FAILURE') + ' - ' + colors.red(resultTable.length) + ' of ' + colors.gray(languages.length) + ' languages failed auto-highlight check.' + - '\n' + - resultTable.toString()); + colors.red('ISSUES') + ' - ' + colors.red(resultTable.length) + ' of ' + colors.gray(languages.length) + ' languages have potential issues.' + + '\n' + + resultTable.toString()); } - - diff --git a/tools/checkTheme.js b/tools/checkTheme.js new file mode 100755 index 0000000000..45ae7d9157 --- /dev/null +++ b/tools/checkTheme.js @@ -0,0 +1,288 @@ +#!/usr/bin/env node + +const fs = require("fs"); +const css = require("css"); +const wcagContrast = require("wcag-contrast"); +const Table = require('cli-table'); +const csscolors = require('css-color-names'); +require("colors"); + +const CODE = { + name: "program code", + scopes: [ + "comment", + "keyword", + "built_in", + "type", + "literal", + "number", + "property", + "regexp", + "string", + "subst", + "symbol", + // "class", + // "function", + "variable", + "title", + "params", + "comment", + "doctag", + "meta", + "attr", + "attribute" + ] +}; + +const OTHER = { + name: "nice to haves (optional, but many grammars use)", + scopes: [ + "meta keyword", + "meta string" + ] +}; + +const HIGH_FIDELITY = { + name: "high fidelity highlighting (this is optional)", + scopes: [ + "title.class", + "title.class.inherited", + "punctuation", + "operator", + "title.function", + "char.escape", + "variable.language" + ] +}; + +const CONFIG = { + required: true, + name: "Config files", + scopes: [ + "meta", + "number", + "string", + "variable", + "keyword", + "section", + "attribute" + ] +}; + +const MARKUP = { + required: true, + name: "Markup (Markdown, etc)", + scopes: [ + "section", + "bullet", + "code", + "emphasis", + "strong", + "formula", + "link", + "quote" + ] +}; + +const CSS = { + name: "CSS/Less/etc", + required: true, + scopes: [ + "attribute", + "string", + "keyword", + "built_in", + "selector-tag", + "selector-id", + "selector-class", + "selector-attr", + "selector-pseudo" + ] +}; + +const TEMPLATES = { + name: "Templates/HTML/XML, etc.", + required: true, + scopes: [ + "tag", + "name", + "attr", + "attribute", + "template-tag", + "template-variable" + ] +}; + +const DIFF = { + name: "Diff", + required: true, + scopes: [ + "meta", + "comment", + "addition", + "deletion" + ] +}; + +function matching_rules(selector, rules) { + const found = []; + rules.forEach(rule => { + if (!rule.selectors) return; + if (rule.selectors.includes(selector)) { + found.push(rule); + } + }); + return found; +} + +function has_rule(selector, rules) { + if (matching_rules(selector, rules).length > 0) return true; + + return false; +} + +function skips_rule(selector, rules) { + return matching_rules(selector, rules) + .some(rule => rule.declarations.length === 0); +} + +const expandScopeName = (name, { prefix }) => { + if (name.includes(".")) { + const pieces = name.split("."); + return [ + `${prefix}${pieces.shift()}`, + ...(pieces.map((x, i) => `${x}${"_".repeat(i + 1)}`)) + ].join("."); + } + return `${prefix}${name}`; +}; + +function scopeToSelector(name) { + return name.split(" ").map(n => expandScopeName(n, { prefix: ".hljs-" })).join(" "); +} + +function check_group(group, rules) { + const has_rules = group.scopes.map(scope => { + const selector = scopeToSelector(scope); + return [scope, has_rule(selector, rules), skips_rule(selector, rules)]; + }); + + + const doesNotSupport = has_rules.map(x => x[1]).includes(false); + const skipped = has_rules.find(x => x[2]); + if (doesNotSupport || skipped) { + console.log(group.name.yellow); + if (doesNotSupport) { + console.log(`- Theme does not fully support.`.brightMagenta); + } + + has_rules.filter(x => !x[1]).forEach(([scope, _]) => { + const selector = scopeToSelector(scope); + console.log(`- scope ${scope.cyan} is not highlighted\n (css: ${selector.green})`); + }); + has_rules.filter(x => x[2]).forEach(([scope, _]) => { + console.log(` - scope ${scope.cyan} [purposely] un-highlighted.`.cyan); + }); + console.log(); + } +} + +const round2 = (x) => Math.round(x*100)/100; + +class CSSRule { + constructor(rule, body) { + this.rule = rule; + if (rule.declarations) { + this.bg = rule.declarations.find(x => x.property == "background-color")?.value; + if (!this.bg) { + this.bg = rule.declarations.find(x => x.property == "background")?.value; + } + this.fg = rule.declarations.find(x => x.property =="color")?.value; + + if (this.bg) { + this.bg = csscolors[this.bg] || this.bg; + } + if (this.fg) { + this.fg = csscolors[this.fg] || this.fg; + } + + // inherit from body if we're missing fg or bg + if (this.hasColor) { + if (!this.bg) this.bg = body.background; + if (!this.fg) this.fg = body.foreground; + } + } + } + get background() { + return this.bg + } + + get foreground() { + return this.fg + } + get hasColor() { + if (!this.rule.declarations) return false; + return this.fg || this.bg; + } + toString() { + return ` ${this.foreground} on ${this.background}` + } + + contrastRatio() { + if (!this.foreground) return "unknown (no fg)" + if (!this.background) return "unknown (no bg)" + return round2(wcagContrast.hex(this.foreground, this.background)); + } +} + +function contrast_report(rules) { + console.log("Accessibility Report".yellow); + + var hljs = rules.find (x => x.selectors && x.selectors.includes(".hljs")); + var body = new CSSRule(hljs); + const table = new Table({ + chars: {'mid': '', 'left-mid': '', 'mid-mid': '', 'right-mid': ''}, + head: ['ratio', 'selector', 'fg', 'bg'], + colWidths: [7, 40, 10, 10], + style: { + head: ['grey'] + } + }); + + rules.forEach(rule => { + var color = new CSSRule(rule, body); + if (!color.hasColor) return; + table.push([ + color.contrastRatio(), + rule.selectors, + color.foreground, + color.background + ]) + // console.log(r.selectors[0], color.contrastRatio(), color.toString()); + }) + console.log(table.toString()) +} + +function validate(data) { + const rules = data.stylesheet.rules; + + check_group(DIFF, rules); + check_group(TEMPLATES, rules); + check_group(CONFIG, rules); + check_group(CSS, rules); + check_group(MARKUP, rules); + check_group(CODE, rules); + check_group(OTHER, rules); + check_group(HIGH_FIDELITY, rules); + + contrast_report(rules); +} + +process.argv.shift(); +process.argv.shift(); + +const file = process.argv[0]; +const content = fs.readFileSync(file).toString(); +const parsed = css.parse(content, {}); + +validate(parsed); diff --git a/tools/codeformat.js b/tools/codeformat.js deleted file mode 100644 index e6c5727338..0000000000 --- a/tools/codeformat.js +++ /dev/null @@ -1,47 +0,0 @@ -'use strict'; - -var _ = require('lodash'); -var bluebird = require('bluebird'); -var path = require('path'); -var glob = bluebird.promisify(require('glob')); -var fs = require('fs'); -var readFile = bluebird.promisify(fs.readFile); -var writeFile = bluebird.promisify(fs.writeFile); -var beautify = require('js-beautify').js_beautify; - -var beautify_options = { - "indent_size": 2, - "indent_char": " ", - "eol": "\n", - "indent_level": 0, - "indent_with_tabs": false, - "preserve_newlines": true, - "max_preserve_newlines": 10, - "jslint_happy": false, - "space_after_anon_function": false, - "brace_style": "collapse", - "keep_array_indentation": false, - "keep_function_indentation": false, - "space_before_conditional": true, - "break_chained_methods": false, - "eval_code": false, - "end_with_newline": true -}; - -console.log("Starting formatting."); - -var sources = path.join('src', 'languages', '*.js'); - -bluebird.map(glob(sources), function (name) { - var basename = path.basename(name, '.js'); - - return readFile(name).then(function (blob) { - return beautify(blob.toString(), beautify_options); - }).then(function (blob) { - return writeFile(name, blob).then(function () { - console.log(" ✓ " + basename); - }); - }); -}).then(function () { - console.log("Finished formatting."); -}); diff --git a/tools/css b/tools/css new file mode 100755 index 0000000000..f18227eb62 --- /dev/null +++ b/tools/css @@ -0,0 +1,15 @@ +#!/bin/bash +# +# tool to help with css refactoring, can perhaps be removed +# once CSS is consistent? or moved to tools? +# +cp test/markup/css/css_consistency.txt test/markup/less +cp test/markup/css/css_consistency.expect.txt test/markup/less +cp test/markup/css/css_consistency.txt test/markup/scss +cp test/markup/css/css_consistency.expect.txt test/markup/scss +cp test/markup/css/css_consistency.txt test/markup/stylus +cp test/markup/css/css_consistency.expect.txt test/markup/stylus + +echo Overall size: +ls -l src/languages/{less,css,scss,stylus}.js src/languages/lib/css-shared.js +ls -l src/languages/{less,css,scss,stylus}.js src/languages/lib/css-shared.js | cut -d' ' -f8 | awk '{s+=$1} END {print s}' diff --git a/tools/developer.html b/tools/developer.html index 79e334540d..8143b8cdc2 100644 --- a/tools/developer.html +++ b/tools/developer.html @@ -7,14 +7,25 @@ -

    highlight.js developer

    - -
    +
    +

    Code

    Language: + Theme:

    -

    Rendered in ... ms [...]

    -
    ...
    +

    Rendered in ... ms [...]

    +
    -

    Markup +

    Markup

    ...
    - - + + + + diff --git a/tools/keywordsformat.js b/tools/keywordsformat.js deleted file mode 100644 index 85d5b22d66..0000000000 --- a/tools/keywordsformat.js +++ /dev/null @@ -1,18 +0,0 @@ -/** Example */ - -var all = 'keywords here'; - -var output = '', line = ''; -all.forEach(function (item) { - if (12 + 1 + line.length + 1 + item.length + 4 > 120) { - output += "\n" + " '" + line + " ' +"; - line = ''; - return; - } - if (line) { - line = line + ' ' + item; - } else { - line = item; - } -}); -console.log(output); diff --git a/tools/lib/bundling.js b/tools/lib/bundling.js index 1983492f83..55360c6ee9 100644 --- a/tools/lib/bundling.js +++ b/tools/lib/bundling.js @@ -1,7 +1,8 @@ -const rollup = require('rollup') +const rollup = require('rollup'); +const crypto = require("crypto"); async function rollupCode(inputOptions, outputOptions) { - const output = await generate(inputOptions, outputOptions) + const output = await generate(inputOptions, outputOptions); return output[0].code; } @@ -17,4 +18,11 @@ async function rollupWrite(inputOptions, outputOptions) { await bundle.write(outputOptions); } -module.exports = { rollupWrite, rollupCode }; +function sha384(contents) { + const hash = crypto.createHash('sha384'); + const data = hash.update(contents, 'utf-8'); + const gen_hash = data.digest('base64'); + return `sha384-${gen_hash}`; +} + +module.exports = { rollupWrite, rollupCode, sha384 }; diff --git a/tools/lib/dependencies.js b/tools/lib/dependencies.js index edc621adf7..c2201d3396 100644 --- a/tools/lib/dependencies.js +++ b/tools/lib/dependencies.js @@ -6,20 +6,19 @@ const DependencyResolver = require('dependency-resolver'); * * @param {array} languages list of languages to be reordered * @returns {array} ordered list of languages -*/ - + */ const reorderDependencies = (languages) => { - let resolver = new DependencyResolver(); - for (let lang of languages) { + const resolver = new DependencyResolver(); + for (const lang of languages) { resolver.add(lang.name); - for (let required of lang.requires) { + for (const required of lang.requires) { resolver.setDependency(lang.name, required); } } return resolver.sort().map((name) => - languages.find((l) => l.name == name) + languages.find((l) => l.name === name) ); -} +}; /** * Filters languages by group (common, etc) @@ -28,14 +27,13 @@ const reorderDependencies = (languages) => { * * @param {array} languages full list of languages * @returns {array} filtered list -*/ - + */ const languagesByGroup = (languages, groupIdentifier) => { - let groupName = groupIdentifier.replace(":",""); + const groupName = groupIdentifier.replace(":", ""); return languages.filter((el) => el.categories.includes(groupName)); -} +}; // :common is a group identifier, "common" is the group name -const isGroup = (id) => id[0] == ":" +const isGroup = (id) => id[0] === ":"; /** @@ -48,39 +46,36 @@ const isGroup = (id) => id[0] == ":" * @param {array} includes - which languages or groups to include * example: ":common elixir ruby" * @returns {array} filtered list if languages -*/ + */ const filter = (allLanguages, includes) => { - if (includes==undefined || includes.length==0) - return reorderDependencies(allLanguages); + if (!includes || includes.length === 0) { return reorderDependencies(allLanguages); } let languages = []; - for (let item of includes) { + for (const item of includes) { if (isGroup(item)) { - languages = languages.concat(languagesByGroup(allLanguages, item)) + languages = languages.concat(languagesByGroup(allLanguages, item)); } else { - let languageName = item; - let found = allLanguages.find((el) => el.name == languageName ) - if (found) - languages.push(found) - else { - console.error(`[ERROR] Language '${languageName}' could not be found.`) - process.exit(1) + const languageName = item; + const found = allLanguages.find((el) => el.name === languageName); + if (found) { languages.push(found); } else { + console.error(`[ERROR] Language '${languageName}' could not be found.`); + process.exit(1); } } } // resolve requires - for (let lang of languages) { + for (const lang of languages) { lang.requires.forEach(needed => { - if (!languages.find((el) => el.name == needed)) { - console.info(`[INFO] Adding ${needed}... ${lang.name} requires ${needed}.`) - languages.push(allLanguages.find((el) => el.name == needed)) + if (!languages.find((el) => el.name === needed)) { + console.info(`[INFO] Adding ${needed}... ${lang.name} requires ${needed}.`); + languages.push(allLanguages.find((el) => el.name === needed)); } }); } // make sure our dependencies are in the correct order return reorderDependencies(languages); -} +}; -module.exports = { reorderDependencies, filter } +module.exports = { reorderDependencies, filter }; diff --git a/tools/lib/external_language.js b/tools/lib/external_language.js index 2115489cde..f099bbbeac 100644 --- a/tools/lib/external_language.js +++ b/tools/lib/external_language.js @@ -1,7 +1,7 @@ -const fs = require("fs") -const fsProm = require("fs").promises -const glob = require("glob-promise") -const path = require("path") +const fs = require("fs"); +const fsProm = require("fs").promises; +const glob = require("glob-promise"); +const path = require("path"); const MODULE_DEFINER = /module\.exports\.definer\s*=/; @@ -12,11 +12,11 @@ class LanguagePackage { // check for language modules in /src/languages async trySrcLanguages() { - let dir = path.join(this.dir,"src/languages/*"); - let languages = await glob(dir); + const dir = path.join(this.dir, "src/languages/*"); + const languages = await glob(dir); if (languages.length > 0) { - this.files = languages.map(fn => path.join(process.cwd(), fn)); - this.names = this.files.map(fn => path.basename(fn,".js")); + this.files = languages.map(fn => `./${fn}`); + this.names = this.files.map(fn => path.basename(fn, ".js")); this._bundle = true; this._valid = true; return true; @@ -41,14 +41,14 @@ class LanguagePackage { // try to find a language module by probing package.json async tryPackageJSON() { - let pack = path.join(this.dir,"package.json"); + const pack = path.join(this.dir, "package.json"); if (fs.existsSync(pack)) { - let data = await fsProm.readFile(pack); - let json = JSON.parse(data); + const data = await fsProm.readFile(pack); + const json = JSON.parse(data); if (json.main) { this.type = "npm"; - let file = path.join(process.cwd(),this.dir, json.main); - let content = await fsProm.readFile(file, { encoding: "utf8" }); + const file = path.join(process.cwd(), this.dir, json.main); + const content = await fsProm.readFile(file, { encoding: "utf8" }); // many existing languages seem to export a `definer` function rather than // simply export the language module directly. This checks for that and if // so allows those existing modules to work "as is" by allowing the build @@ -58,7 +58,7 @@ class LanguagePackage { this.loader = "definer"; } this.files = [file]; - this.names = [path.basename(file,".js")]; + this.names = [path.basename(file, ".js")]; this._valid = true; return true; } @@ -71,17 +71,17 @@ class LanguagePackage { // any bundle with files in ROOT/src/languages/ will be considered a potential // multi language bundle and any files in that directy will be considered to be // language modules - await this.trySrcLanguages() || + await this.trySrcLanguages() // otherwise we fall back to looking for a package.json and whatever it's // `main` entry point is that is the file we assume the language module is // defined in - await this.tryPackageJSON(); + || await this.tryPackageJSON(); this._detected = true; } async valid() { if (!this._detected) { - await this.detect() + await this.detect(); } return this._valid; } @@ -90,16 +90,16 @@ class LanguagePackage { // third party language modules are dropped into the `highlight-js/extra` // folder and will be auto-detected by the build system async function getThirdPartyPackages() { - let packages = []; - let otherPackages = await glob("./extra/*"); - for (let packageDir of otherPackages) { - let thirdPartyPackage = new LanguagePackage(packageDir) - let valid = await thirdPartyPackage.valid(); + const packages = []; + const otherPackages = await glob("./extra/*"); + for (const packageDir of otherPackages) { + const thirdPartyPackage = new LanguagePackage(packageDir); + const valid = await thirdPartyPackage.valid(); if (valid) { - packages.push(thirdPartyPackage) + packages.push(thirdPartyPackage); } } return packages; } -module.exports = { LanguagePackage, getThirdPartyPackages} +module.exports = { LanguagePackage, getThirdPartyPackages }; diff --git a/tools/lib/language.js b/tools/lib/language.js index 5b237c101a..8da727e101 100644 --- a/tools/lib/language.js +++ b/tools/lib/language.js @@ -1,36 +1,35 @@ -const fs = require("fs") -const fsProm = require("fs").promises +const fs = require("fs"); const Terser = require("terser"); -const glob = require("glob") -const path = require("path") -const build_config = require("../build_config") +const glob = require("glob"); +const path = require("path"); +const build_config = require("../build_config.js"); -const REQUIRES_REGEX = /\/\*.*?Requires: (.*?)\n/s -const CATEGORY_REGEX = /\/\*.*?Category: (.*?)\n/s -const LANGUAGE_REGEX = /\/\*.*?Language: (.*?)\n/s -const {rollupCode} = require("./bundling.js") -const { getThirdPartyPackages } = require("./external_language") +const packageJSON = require("../../package.json"); +const REQUIRES_REGEX = /\/\*.*?Requires: (.*?)\r?\n/s; +const CATEGORY_REGEX = /\/\*.*?Category: (.*?)\r?\n/s; +const LANGUAGE_REGEX = /\/\*.*?Language: (.*?)\r?\n/s; +const { rollupCode } = require("./bundling.js"); +const { getThirdPartyPackages } = require("./external_language.js"); class Language { - constructor(name, path) { - this.name = name - this.prettyName = name - this.requires = [] - this.categories = [] - this.third_party = false + this.name = name; + this.prettyName = name; + this.requires = []; + this.categories = []; + this.third_party = false; // compiled code - this.module = "" - this.minified = "" + this.module = ""; + this.minified = ""; - this.path = path - this.data = fs.readFileSync(path, {encoding: "utf8"}) - this.loadMetadata() + this.path = path; + this.data = fs.readFileSync(path, { encoding: "utf8" }); + this.loadMetadata(); } async compile(options) { - await compileLanguage(this,options); + await compileLanguage(this, options); return this; } @@ -38,71 +37,75 @@ class Language { if (this._sample) return this._sample; this._sample = ""; - if (fs.existsSync(this.samplePath)) - this._sample = fs.readFileSync(this.samplePath, {encoding: "utf8"}); + if (fs.existsSync(this.samplePath)) { this._sample = fs.readFileSync(this.samplePath, { encoding: "utf8" }); } return this._sample; } get samplePath() { - return `./test/detect/${this.name}/default.txt` + if (this.moduleDir) { + // this is the 'extras' case. + return `${this.moduleDir}/test/detect/${this.name}/default.txt`; + } else { + // this is the common/built-in case. + return `./test/detect/${this.name}/default.txt`; + } } loadMetadata() { - var requiresMatch = REQUIRES_REGEX.exec(this.data) - var categoryMatch = CATEGORY_REGEX.exec(this.data) - var languageMatch = LANGUAGE_REGEX.exec(this.data) + const requiresMatch = REQUIRES_REGEX.exec(this.data); + const categoryMatch = CATEGORY_REGEX.exec(this.data); + const languageMatch = LANGUAGE_REGEX.exec(this.data); - if (requiresMatch) - this.requires = requiresMatch[1].split(", ").map((n) => n.replace(".js","")) + if (requiresMatch) { this.requires = requiresMatch[1].split(", ").map((n) => n.replace(".js", "")); } - if (categoryMatch) - this.categories = categoryMatch[1].split(/,\s?/) + if (categoryMatch) { this.categories = categoryMatch[1].split(/,\s?/); } - if (languageMatch) - this.prettyName = languageMatch[1] + if (languageMatch) { this.prettyName = languageMatch[1]; } } static fromFile(filename) { - if (filename.startsWith("/") || filename.startsWith(".")) - { - var file = filename - } else { - var file = `./src/languages/${filename}` - } return new Language( - path.basename(filename).replace(".js",""), - file - ) + path.basename(filename).replace(".js", ""), + filename + ); } } -async function compileLanguage (language, options) { - const EXPORT_REGEX = /export default (.*);/; - const IIFE_HEADER_REGEX = /^(var dummyName = )?\(function \(\)/; +async function compileLanguage(language, options) { + const HEADER = `/*! \`${language.name}\` grammar compiled for Highlight.js ${packageJSON.version} */`; // TODO: cant we use the source we already have? - const input = { ...build_config.rollup.browser.input, input: language.path }; - const output = { ...build_config.rollup.browser.output, name: `dummyName`, file: "out.js" }; - var data = await rollupCode(input, output) - - data = data.replace(IIFE_HEADER_REGEX, `hljs.registerLanguage('${language.name}', function ()`) - - var original = data; - language.module = data; - data = Terser.minify(data, options["terser"]); - language.minified = data.code || original; + const input = { ...build_config.rollup.browser_iife.input, input: language.path }; + const output = { ...build_config.rollup.browser_iife.output, name: `hljsGrammar`, file: "out.js" }; + output.footer = null; + + const data = await rollupCode(input, output); + const iife = ` + ${HEADER} + (function(){ + ${data} + hljs.registerLanguage('${language.name}', hljsGrammar); + })(); + `.trim(); + const esm = `${HEADER}\n${data};\nexport default hljsGrammar;`; + + language.module = iife; + const miniESM = await Terser.minify(esm, options.terser); + const miniIIFE = await Terser.minify(iife, options.terser); + language.minified = miniIIFE.code; + language.minifiedESM = miniESM.code; } async function getLanguages() { - let languages = []; + const languages = []; glob.sync("./src/languages/*.js").forEach((file) => { languages.push(Language.fromFile(file)); }); - let extraPackages = await getThirdPartyPackages(); - for (let ext of extraPackages) { - for (let file of ext.files) { - let l = Language.fromFile(file); + const extraPackages = await getThirdPartyPackages(); + for (const ext of extraPackages) { + for (const file of ext.files) { + const l = Language.fromFile(file); l.loader = ext.loader; l.third_party = true; l.moduleDir = ext.dir; diff --git a/tools/lib/makestuff.js b/tools/lib/makestuff.js index 2dde6cc376..9f3f7da707 100644 --- a/tools/lib/makestuff.js +++ b/tools/lib/makestuff.js @@ -1,34 +1,47 @@ const fs = require("fs"); const CleanCSS = require('clean-css'); const path = require('path'); -const _ = require('lodash'); -const config = require("../build_config"); -const del = require('del'); +const _ = require('lodash'); +const del = require('del'); +const config = require("../build_config.js"); async function clean(directory) { - del.sync([directory]) - fs.mkdirSync(directory, {recursive: true}); -}; + del.sync([directory]); + fs.mkdirSync(directory, { recursive: true }); +} -function install(file, dest=file) { +function install(file, dest = file) { fs.copyFileSync(file, `${process.env.BUILD_DIR}/${dest}`); } -function install_cleancss(file, dest) { - let content = fs.readFileSync(file, {encoding: "utf8"}); - let out = new CleanCSS(config.clean_css).minify(content).styles; +const DEFAULT_CSS = ` +pre code.hljs { + display: block; + overflow-x: auto; + padding: 1em; +} + +code.hljs { + padding: 3px 5px; +} +`.trim(); + +function installCleanCSS(file, dest) { + const theme = fs.readFileSync(file, { encoding: "utf8" }); + const content = DEFAULT_CSS + "\n" + theme; + const out = new CleanCSS(config.clean_css).minify(content).styles; fs.writeFileSync(`${process.env.BUILD_DIR}/${dest}`, out); } function mkdir(dirname) { - fs.mkdirSync(`${process.env.BUILD_DIR}/${dirname}`, {recursive: true}); + fs.mkdirSync(`${process.env.BUILD_DIR}/${dirname}`, { recursive: true }); } function renderTemplate(src, dest, data) { data.path = path; - let content = fs.readFileSync(src, {encoding: "utf8"}); - let rendered = _.template(content)(data); + const content = fs.readFileSync(src, { encoding: "utf8" }); + const rendered = _.template(content)(data); fs.writeFileSync(`${process.env.BUILD_DIR}/${dest}`, rendered); } -module.exports = { clean, install, install_cleancss, mkdir, renderTemplate }; +module.exports = { clean, install, installCleanCSS, mkdir, renderTemplate }; diff --git a/tools/perf.js b/tools/perf.js new file mode 100755 index 0000000000..3c37ead0a1 --- /dev/null +++ b/tools/perf.js @@ -0,0 +1,70 @@ +#!/usr/bin/env node +const execSync = require('child_process').execSync; +const fs = require('fs'); +const { performance } = require('perf_hooks'); + +const build = () => { + console.log(`Starting perf tests, building hljs ... `); + // build node.js version of library with CJS and ESM libraries + execSync('npm run build', { + cwd: '.', + env: Object.assign( + process.env + ) + }); +}; + +const timeTest = (name, func) => { + process.stdout.write(` running ${name}...`); + const t0 = performance.now(); + func(); + const t1 = performance.now(); + console.log(` done! [${((t1 - t0) / 1000).toFixed(2)}s elapsed]`); +}; + +const oneLanguageMarkupTests = (lang) => { + for (let i = 0; i < 50; i++) { + execSync('npx mocha ./test/markup', { + cwd: '.', + env: Object.assign( + process.env, + { ONLY_LANGUAGES: lang } + ) + }); + } +}; + +const oneLanguageCheckAutoDetect = (lang) => { + for (let i = 0; i < 50; i++) { + execSync('node ./tools/checkAutoDetect.js', { + env: Object.assign( + process.env, + { ONLY_LANGUAGES: lang } + ) + }); + } +}; + +const globalCheckAutoDetect = () => { + for (let i = 0; i < 5; i++) { + execSync('node ./tools/checkAutoDetect.js'); + } +}; + +const highlightFile = (lang) => { + const source = fs.readFileSync(`./tools/sample_files/${lang}.txt`, { encoding: 'utf8' }); + const hljs = require('../build.js'); + for (let i = 0; i < 2000; i++) { + hljs.highlight(source, { language: lang }); + } +}; + +const main = (lang) => { + build(); + timeTest(`global checkAutoDetect`, globalCheckAutoDetect); + timeTest(`${lang}-only markup tests`, () => oneLanguageMarkupTests(lang)); + timeTest(`${lang}-only checkAutoDetect`, () => oneLanguageCheckAutoDetect(lang)); + timeTest(`highlight large file`, () => highlightFile(lang)); +}; + +main('python'); diff --git a/tools/sample_files/python.txt b/tools/sample_files/python.txt new file mode 100644 index 0000000000..c70ad5a014 --- /dev/null +++ b/tools/sample_files/python.txt @@ -0,0 +1,1713 @@ +from pdb import set_trace +import collections +import re +import sys +import warnings +from bs4.dammit import EntitySubstitution + +DEFAULT_OUTPUT_ENCODING = "utf-8" +PY3K = (sys.version_info[0] > 2) + +whitespace_re = re.compile("\s+") + +def _alias(attr): + """Alias one attribute name to another for backward compatibility""" + @property + def alias(self): + return getattr(self, attr) + + @alias.setter + def alias(self): + return setattr(self, attr) + return alias + + +class NamespacedAttribute(unicode): + + def __new__(cls, prefix, name, namespace=None): + if name is None: + obj = unicode.__new__(cls, prefix) + elif prefix is None: + # Not really namespaced. + obj = unicode.__new__(cls, name) + else: + obj = unicode.__new__(cls, prefix + ":" + name) + obj.prefix = prefix + obj.name = name + obj.namespace = namespace + return obj + +class AttributeValueWithCharsetSubstitution(unicode): + """A stand-in object for a character encoding specified in HTML.""" + +class CharsetMetaAttributeValue(AttributeValueWithCharsetSubstitution): + """A generic stand-in for the value of a meta tag's 'charset' attribute. + + When Beautiful Soup parses the markup '', the + value of the 'charset' attribute will be one of these objects. + """ + + def __new__(cls, original_value): + obj = unicode.__new__(cls, original_value) + obj.original_value = original_value + return obj + + def encode(self, encoding): + return encoding + + +class ContentMetaAttributeValue(AttributeValueWithCharsetSubstitution): + """A generic stand-in for the value of a meta tag's 'content' attribute. + + When Beautiful Soup parses the markup: + + + The value of the 'content' attribute will be one of these objects. + """ + + CHARSET_RE = re.compile("((^|;)\s*charset=)([^;]*)", re.M) + + def __new__(cls, original_value): + match = cls.CHARSET_RE.search(original_value) + if match is None: + # No substitution necessary. + return unicode.__new__(unicode, original_value) + + obj = unicode.__new__(cls, original_value) + obj.original_value = original_value + return obj + + def encode(self, encoding): + def rewrite(match): + return match.group(1) + encoding + return self.CHARSET_RE.sub(rewrite, self.original_value) + +class HTMLAwareEntitySubstitution(EntitySubstitution): + + """Entity substitution rules that are aware of some HTML quirks. + + Specifically, the contents of + + +``` + +The full list of digests for every file can be found below. + +### Digests + +``` + +``` diff --git a/tools/utility.js b/tools/utility.js deleted file mode 100644 index c244d67114..0000000000 --- a/tools/utility.js +++ /dev/null @@ -1,74 +0,0 @@ -'use strict'; - -let regex = {}; - -const REPLACES = { - 'case_insensitive': 'cI', - 'lexemes': 'l', - 'contains': 'c', - 'keywords': 'k', - 'subLanguage': 'sL', - 'className': 'cN', - 'begin': 'b', - 'beginKeywords': 'bK', - 'end': 'e', - 'endsWithParent': 'eW', - 'illegal': 'i', - 'excludeBegin': 'eB', - 'excludeEnd': 'eE', - 'returnBegin': 'rB', - 'returnEnd': 'rE', - 'variants': 'v', - - 'IDENT_RE': 'IR', - 'UNDERSCORE_IDENT_RE': 'UIR', - 'NUMBER_RE': 'NR', - 'C_NUMBER_RE': 'CNR', - 'BINARY_NUMBER_RE': 'BNR', - 'RE_STARTERS_RE': 'RSR', - 'BACKSLASH_ESCAPE': 'BE', - 'APOS_STRING_MODE': 'ASM', - 'QUOTE_STRING_MODE': 'QSM', - 'PHRASAL_WORDS_MODE': 'PWM', - 'C_LINE_COMMENT_MODE': 'CLCM', - 'C_BLOCK_COMMENT_MODE': 'CBCM', - 'HASH_COMMENT_MODE': 'HCM', - 'NUMBER_MODE': 'NM', - 'C_NUMBER_MODE': 'CNM', - 'BINARY_NUMBER_MODE': 'BNM', - 'CSS_NUMBER_MODE': 'CSSNM', - 'REGEXP_MODE': 'RM', - 'TITLE_MODE': 'TM', - 'UNDERSCORE_TITLE_MODE': 'UTM', - 'COMMENT': 'C', - - 'beginRe': 'bR', - 'endRe': 'eR', - 'illegalRe': 'iR', - 'lexemesRe': 'lR', - 'terminators': 't', - 'terminator_end': 'tE' -}; - -regex.replaces = new RegExp( - `(?:([\\w\\d]+)\\.(${Object.keys(REPLACES).filter(r => r.toUpperCase() === r).join('|')})\\s*=(?!=)|\\b(${Object.keys(REPLACES).join('|')})\\b)`, 'g'); - -regex.apiReplacesFrom = /\bvar\s*API_REPLACES\b/; -regex.apiReplacesTo = `var API_REPLACES = ${JSON.stringify(REPLACES)}`; - -function replaceClassNames(match, gDeclObj, gDeclKey) { - if(gDeclObj) - return replaceAndSaveClassNames(gDeclObj, gDeclKey); - else - return REPLACES[match]; -} - -function replaceAndSaveClassNames(obj, key) { - return `${obj}.${REPLACES[key]} = ${obj}.${key} =`; -} - -module.exports = { - regex: regex, - replaceClassNames: replaceClassNames, - REPLACES: REPLACES -}; diff --git a/tools/vendor/highlight-vue.min.js b/tools/vendor/highlight-vue.min.js new file mode 100644 index 0000000000..efb83d8912 --- /dev/null +++ b/tools/vendor/highlight-vue.min.js @@ -0,0 +1 @@ +var hljsVuePlugin=function(e){"use strict";const t={props:["language","code","autodetect"],data:function(){return{detectedLanguage:"",unknownLanguage:!1}},computed:{className(){return this.unknownLanguage?"":"hljs "+this.detectedLanguage},highlighted(){if(!this.autoDetect&&!e.getLanguage(this.language))return console.warn(`The language "${this.language}" you specified could not be found.`),this.unknownLanguage=!0,this.code.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'");let t={};return this.autoDetect?(t=e.highlightAuto(this.code),this.detectedLanguage=t.language):(t=e.highlight(this.language,this.code,this.ignoreIllegals),this.detectedLanguage=this.language),t.value},autoDetect(){return!this.language||(e=this.autodetect,Boolean(e||""===e));var e},ignoreIllegals:()=>!0},render(e){return e("pre",{},[e("code",{class:this.className,domProps:{innerHTML:this.highlighted}})])}};return{install(e){e.component("highlightjs",t)},component:t}}(hljs);var hljsVuePlugin=function(e){"use strict";const t={props:["language","code","autodetect"],data:function(){return{detectedLanguage:"",unknownLanguage:!1}},computed:{className(){return this.unknownLanguage?"":"hljs "+this.detectedLanguage},highlighted(){if(!this.autoDetect&&!e.getLanguage(this.language))return console.warn(`The language "${this.language}" you specified could not be found.`),this.unknownLanguage=!0,this.code.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'");let t={};return this.autoDetect?(t=e.highlightAuto(this.code),this.detectedLanguage=t.language):(t=e.highlight(this.language,this.code,this.ignoreIllegals),this.detectedLanguage=this.language),t.value},autoDetect(){return!this.language||(e=this.autodetect,Boolean(e||""===e));var e},ignoreIllegals:()=>!0},render(e){return e("pre",{},[e("code",{class:this.className,domProps:{innerHTML:this.highlighted}})])}};return{install(e){e.component("highlightjs",t)},component:t}}(hljs); diff --git a/demo/jquery-2.1.1.min.js b/tools/vendor/jquery-2.1.1.min.js similarity index 100% rename from demo/jquery-2.1.1.min.js rename to tools/vendor/jquery-2.1.1.min.js diff --git a/tools/vendor/vue.js b/tools/vendor/vue.js new file mode 100644 index 0000000000..e22cf13003 --- /dev/null +++ b/tools/vendor/vue.js @@ -0,0 +1,11965 @@ +/*! + * Vue.js v2.6.11 + * (c) 2014-2019 Evan You + * Released under the MIT License. + */ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global = global || self, global.Vue = factory()); +}(this, function () { 'use strict'; + + /* */ + + var emptyObject = Object.freeze({}); + + // These helpers produce better VM code in JS engines due to their + // explicitness and function inlining. + function isUndef (v) { + return v === undefined || v === null + } + + function isDef (v) { + return v !== undefined && v !== null + } + + function isTrue (v) { + return v === true + } + + function isFalse (v) { + return v === false + } + + /** + * Check if value is primitive. + */ + function isPrimitive (value) { + return ( + typeof value === 'string' || + typeof value === 'number' || + // $flow-disable-line + typeof value === 'symbol' || + typeof value === 'boolean' + ) + } + + /** + * Quick object check - this is primarily used to tell + * Objects from primitive values when we know the value + * is a JSON-compliant type. + */ + function isObject (obj) { + return obj !== null && typeof obj === 'object' + } + + /** + * Get the raw type string of a value, e.g., [object Object]. + */ + var _toString = Object.prototype.toString; + + function toRawType (value) { + return _toString.call(value).slice(8, -1) + } + + /** + * Strict object type check. Only returns true + * for plain JavaScript objects. + */ + function isPlainObject (obj) { + return _toString.call(obj) === '[object Object]' + } + + function isRegExp (v) { + return _toString.call(v) === '[object RegExp]' + } + + /** + * Check if val is a valid array index. + */ + function isValidArrayIndex (val) { + var n = parseFloat(String(val)); + return n >= 0 && Math.floor(n) === n && isFinite(val) + } + + function isPromise (val) { + return ( + isDef(val) && + typeof val.then === 'function' && + typeof val.catch === 'function' + ) + } + + /** + * Convert a value to a string that is actually rendered. + */ + function toString (val) { + return val == null + ? '' + : Array.isArray(val) || (isPlainObject(val) && val.toString === _toString) + ? JSON.stringify(val, null, 2) + : String(val) + } + + /** + * Convert an input value to a number for persistence. + * If the conversion fails, return original string. + */ + function toNumber (val) { + var n = parseFloat(val); + return isNaN(n) ? val : n + } + + /** + * Make a map and return a function for checking if a key + * is in that map. + */ + function makeMap ( + str, + expectsLowerCase + ) { + var map = Object.create(null); + var list = str.split(','); + for (var i = 0; i < list.length; i++) { + map[list[i]] = true; + } + return expectsLowerCase + ? function (val) { return map[val.toLowerCase()]; } + : function (val) { return map[val]; } + } + + /** + * Check if a tag is a built-in tag. + */ + var isBuiltInTag = makeMap('slot,component', true); + + /** + * Check if an attribute is a reserved attribute. + */ + var isReservedAttribute = makeMap('key,ref,slot,slot-scope,is'); + + /** + * Remove an item from an array. + */ + function remove (arr, item) { + if (arr.length) { + var index = arr.indexOf(item); + if (index > -1) { + return arr.splice(index, 1) + } + } + } + + /** + * Check whether an object has the property. + */ + var hasOwnProperty = Object.prototype.hasOwnProperty; + function hasOwn (obj, key) { + return hasOwnProperty.call(obj, key) + } + + /** + * Create a cached version of a pure function. + */ + function cached (fn) { + var cache = Object.create(null); + return (function cachedFn (str) { + var hit = cache[str]; + return hit || (cache[str] = fn(str)) + }) + } + + /** + * Camelize a hyphen-delimited string. + */ + var camelizeRE = /-(\w)/g; + var camelize = cached(function (str) { + return str.replace(camelizeRE, function (_, c) { return c ? c.toUpperCase() : ''; }) + }); + + /** + * Capitalize a string. + */ + var capitalize = cached(function (str) { + return str.charAt(0).toUpperCase() + str.slice(1) + }); + + /** + * Hyphenate a camelCase string. + */ + var hyphenateRE = /\B([A-Z])/g; + var hyphenate = cached(function (str) { + return str.replace(hyphenateRE, '-$1').toLowerCase() + }); + + /** + * Simple bind polyfill for environments that do not support it, + * e.g., PhantomJS 1.x. Technically, we don't need this anymore + * since native bind is now performant enough in most browsers. + * But removing it would mean breaking code that was able to run in + * PhantomJS 1.x, so this must be kept for backward compatibility. + */ + + /* istanbul ignore next */ + function polyfillBind (fn, ctx) { + function boundFn (a) { + var l = arguments.length; + return l + ? l > 1 + ? fn.apply(ctx, arguments) + : fn.call(ctx, a) + : fn.call(ctx) + } + + boundFn._length = fn.length; + return boundFn + } + + function nativeBind (fn, ctx) { + return fn.bind(ctx) + } + + var bind = Function.prototype.bind + ? nativeBind + : polyfillBind; + + /** + * Convert an Array-like object to a real Array. + */ + function toArray (list, start) { + start = start || 0; + var i = list.length - start; + var ret = new Array(i); + while (i--) { + ret[i] = list[i + start]; + } + return ret + } + + /** + * Mix properties into target object. + */ + function extend (to, _from) { + for (var key in _from) { + to[key] = _from[key]; + } + return to + } + + /** + * Merge an Array of Objects into a single Object. + */ + function toObject (arr) { + var res = {}; + for (var i = 0; i < arr.length; i++) { + if (arr[i]) { + extend(res, arr[i]); + } + } + return res + } + + /* eslint-disable no-unused-vars */ + + /** + * Perform no operation. + * Stubbing args to make Flow happy without leaving useless transpiled code + * with ...rest (https://flow.org/blog/2017/05/07/Strict-Function-Call-Arity/). + */ + function noop (a, b, c) {} + + /** + * Always return false. + */ + var no = function (a, b, c) { return false; }; + + /* eslint-enable no-unused-vars */ + + /** + * Return the same value. + */ + var identity = function (_) { return _; }; + + /** + * Generate a string containing static keys from compiler modules. + */ + function genStaticKeys (modules) { + return modules.reduce(function (keys, m) { + return keys.concat(m.staticKeys || []) + }, []).join(',') + } + + /** + * Check if two values are loosely equal - that is, + * if they are plain objects, do they have the same shape? + */ + function looseEqual (a, b) { + if (a === b) { return true } + var isObjectA = isObject(a); + var isObjectB = isObject(b); + if (isObjectA && isObjectB) { + try { + var isArrayA = Array.isArray(a); + var isArrayB = Array.isArray(b); + if (isArrayA && isArrayB) { + return a.length === b.length && a.every(function (e, i) { + return looseEqual(e, b[i]) + }) + } else if (a instanceof Date && b instanceof Date) { + return a.getTime() === b.getTime() + } else if (!isArrayA && !isArrayB) { + var keysA = Object.keys(a); + var keysB = Object.keys(b); + return keysA.length === keysB.length && keysA.every(function (key) { + return looseEqual(a[key], b[key]) + }) + } else { + /* istanbul ignore next */ + return false + } + } catch (e) { + /* istanbul ignore next */ + return false + } + } else if (!isObjectA && !isObjectB) { + return String(a) === String(b) + } else { + return false + } + } + + /** + * Return the first index at which a loosely equal value can be + * found in the array (if value is a plain object, the array must + * contain an object of the same shape), or -1 if it is not present. + */ + function looseIndexOf (arr, val) { + for (var i = 0; i < arr.length; i++) { + if (looseEqual(arr[i], val)) { return i } + } + return -1 + } + + /** + * Ensure a function is called only once. + */ + function once (fn) { + var called = false; + return function () { + if (!called) { + called = true; + fn.apply(this, arguments); + } + } + } + + var SSR_ATTR = 'data-server-rendered'; + + var ASSET_TYPES = [ + 'component', + 'directive', + 'filter' + ]; + + var LIFECYCLE_HOOKS = [ + 'beforeCreate', + 'created', + 'beforeMount', + 'mounted', + 'beforeUpdate', + 'updated', + 'beforeDestroy', + 'destroyed', + 'activated', + 'deactivated', + 'errorCaptured', + 'serverPrefetch' + ]; + + /* */ + + + + var config = ({ + /** + * Option merge strategies (used in core/util/options) + */ + // $flow-disable-line + optionMergeStrategies: Object.create(null), + + /** + * Whether to suppress warnings. + */ + silent: false, + + /** + * Show production mode tip message on boot? + */ + productionTip: "development" !== 'production', + + /** + * Whether to enable devtools + */ + devtools: "development" !== 'production', + + /** + * Whether to record perf + */ + performance: false, + + /** + * Error handler for watcher errors + */ + errorHandler: null, + + /** + * Warn handler for watcher warns + */ + warnHandler: null, + + /** + * Ignore certain custom elements + */ + ignoredElements: [], + + /** + * Custom user key aliases for v-on + */ + // $flow-disable-line + keyCodes: Object.create(null), + + /** + * Check if a tag is reserved so that it cannot be registered as a + * component. This is platform-dependent and may be overwritten. + */ + isReservedTag: no, + + /** + * Check if an attribute is reserved so that it cannot be used as a component + * prop. This is platform-dependent and may be overwritten. + */ + isReservedAttr: no, + + /** + * Check if a tag is an unknown element. + * Platform-dependent. + */ + isUnknownElement: no, + + /** + * Get the namespace of an element + */ + getTagNamespace: noop, + + /** + * Parse the real tag name for the specific platform. + */ + parsePlatformTagName: identity, + + /** + * Check if an attribute must be bound using property, e.g. value + * Platform-dependent. + */ + mustUseProp: no, + + /** + * Perform updates asynchronously. Intended to be used by Vue Test Utils + * This will significantly reduce performance if set to false. + */ + async: true, + + /** + * Exposed for legacy reasons + */ + _lifecycleHooks: LIFECYCLE_HOOKS + }); + + /* */ + + /** + * unicode letters used for parsing html tags, component names and property paths. + * using https://www.w3.org/TR/html53/semantics-scripting.html#potentialcustomelementname + * skipping \u10000-\uEFFFF due to it freezing up PhantomJS + */ + var unicodeRegExp = /a-zA-Z\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD/; + + /** + * Check if a string starts with $ or _ + */ + function isReserved (str) { + var c = (str + '').charCodeAt(0); + return c === 0x24 || c === 0x5F + } + + /** + * Define a property. + */ + function def (obj, key, val, enumerable) { + Object.defineProperty(obj, key, { + value: val, + enumerable: !!enumerable, + writable: true, + configurable: true + }); + } + + /** + * Parse simple path. + */ + var bailRE = new RegExp(("[^" + (unicodeRegExp.source) + ".$_\\d]")); + function parsePath (path) { + if (bailRE.test(path)) { + return + } + var segments = path.split('.'); + return function (obj) { + for (var i = 0; i < segments.length; i++) { + if (!obj) { return } + obj = obj[segments[i]]; + } + return obj + } + } + + /* */ + + // can we use __proto__? + var hasProto = '__proto__' in {}; + + // Browser environment sniffing + var inBrowser = typeof window !== 'undefined'; + var inWeex = typeof WXEnvironment !== 'undefined' && !!WXEnvironment.platform; + var weexPlatform = inWeex && WXEnvironment.platform.toLowerCase(); + var UA = inBrowser && window.navigator.userAgent.toLowerCase(); + var isIE = UA && /msie|trident/.test(UA); + var isIE9 = UA && UA.indexOf('msie 9.0') > 0; + var isEdge = UA && UA.indexOf('edge/') > 0; + var isAndroid = (UA && UA.indexOf('android') > 0) || (weexPlatform === 'android'); + var isIOS = (UA && /iphone|ipad|ipod|ios/.test(UA)) || (weexPlatform === 'ios'); + var isChrome = UA && /chrome\/\d+/.test(UA) && !isEdge; + var isPhantomJS = UA && /phantomjs/.test(UA); + var isFF = UA && UA.match(/firefox\/(\d+)/); + + // Firefox has a "watch" function on Object.prototype... + var nativeWatch = ({}).watch; + + var supportsPassive = false; + if (inBrowser) { + try { + var opts = {}; + Object.defineProperty(opts, 'passive', ({ + get: function get () { + /* istanbul ignore next */ + supportsPassive = true; + } + })); // https://github.com/facebook/flow/issues/285 + window.addEventListener('test-passive', null, opts); + } catch (e) {} + } + + // this needs to be lazy-evaled because vue may be required before + // vue-server-renderer can set VUE_ENV + var _isServer; + var isServerRendering = function () { + if (_isServer === undefined) { + /* istanbul ignore if */ + if (!inBrowser && !inWeex && typeof global !== 'undefined') { + // detect presence of vue-server-renderer and avoid + // Webpack shimming the process + _isServer = global['process'] && global['process'].env.VUE_ENV === 'server'; + } else { + _isServer = false; + } + } + return _isServer + }; + + // detect devtools + var devtools = inBrowser && window.__VUE_DEVTOOLS_GLOBAL_HOOK__; + + /* istanbul ignore next */ + function isNative (Ctor) { + return typeof Ctor === 'function' && /native code/.test(Ctor.toString()) + } + + var hasSymbol = + typeof Symbol !== 'undefined' && isNative(Symbol) && + typeof Reflect !== 'undefined' && isNative(Reflect.ownKeys); + + var _Set; + /* istanbul ignore if */ // $flow-disable-line + if (typeof Set !== 'undefined' && isNative(Set)) { + // use native Set when available. + _Set = Set; + } else { + // a non-standard Set polyfill that only works with primitive keys. + _Set = /*@__PURE__*/(function () { + function Set () { + this.set = Object.create(null); + } + Set.prototype.has = function has (key) { + return this.set[key] === true + }; + Set.prototype.add = function add (key) { + this.set[key] = true; + }; + Set.prototype.clear = function clear () { + this.set = Object.create(null); + }; + + return Set; + }()); + } + + /* */ + + var warn = noop; + var tip = noop; + var generateComponentTrace = (noop); // work around flow check + var formatComponentName = (noop); + + { + var hasConsole = typeof console !== 'undefined'; + var classifyRE = /(?:^|[-_])(\w)/g; + var classify = function (str) { return str + .replace(classifyRE, function (c) { return c.toUpperCase(); }) + .replace(/[-_]/g, ''); }; + + warn = function (msg, vm) { + var trace = vm ? generateComponentTrace(vm) : ''; + + if (config.warnHandler) { + config.warnHandler.call(null, msg, vm, trace); + } else if (hasConsole && (!config.silent)) { + console.error(("[Vue warn]: " + msg + trace)); + } + }; + + tip = function (msg, vm) { + if (hasConsole && (!config.silent)) { + console.warn("[Vue tip]: " + msg + ( + vm ? generateComponentTrace(vm) : '' + )); + } + }; + + formatComponentName = function (vm, includeFile) { + if (vm.$root === vm) { + return '' + } + var options = typeof vm === 'function' && vm.cid != null + ? vm.options + : vm._isVue + ? vm.$options || vm.constructor.options + : vm; + var name = options.name || options._componentTag; + var file = options.__file; + if (!name && file) { + var match = file.match(/([^/\\]+)\.vue$/); + name = match && match[1]; + } + + return ( + (name ? ("<" + (classify(name)) + ">") : "") + + (file && includeFile !== false ? (" at " + file) : '') + ) + }; + + var repeat = function (str, n) { + var res = ''; + while (n) { + if (n % 2 === 1) { res += str; } + if (n > 1) { str += str; } + n >>= 1; + } + return res + }; + + generateComponentTrace = function (vm) { + if (vm._isVue && vm.$parent) { + var tree = []; + var currentRecursiveSequence = 0; + while (vm) { + if (tree.length > 0) { + var last = tree[tree.length - 1]; + if (last.constructor === vm.constructor) { + currentRecursiveSequence++; + vm = vm.$parent; + continue + } else if (currentRecursiveSequence > 0) { + tree[tree.length - 1] = [last, currentRecursiveSequence]; + currentRecursiveSequence = 0; + } + } + tree.push(vm); + vm = vm.$parent; + } + return '\n\nfound in\n\n' + tree + .map(function (vm, i) { return ("" + (i === 0 ? '---> ' : repeat(' ', 5 + i * 2)) + (Array.isArray(vm) + ? ((formatComponentName(vm[0])) + "... (" + (vm[1]) + " recursive calls)") + : formatComponentName(vm))); }) + .join('\n') + } else { + return ("\n\n(found in " + (formatComponentName(vm)) + ")") + } + }; + } + + /* */ + + var uid = 0; + + /** + * A dep is an observable that can have multiple + * directives subscribing to it. + */ + var Dep = function Dep () { + this.id = uid++; + this.subs = []; + }; + + Dep.prototype.addSub = function addSub (sub) { + this.subs.push(sub); + }; + + Dep.prototype.removeSub = function removeSub (sub) { + remove(this.subs, sub); + }; + + Dep.prototype.depend = function depend () { + if (Dep.target) { + Dep.target.addDep(this); + } + }; + + Dep.prototype.notify = function notify () { + // stabilize the subscriber list first + var subs = this.subs.slice(); + if (!config.async) { + // subs aren't sorted in scheduler if not running async + // we need to sort them now to make sure they fire in correct + // order + subs.sort(function (a, b) { return a.id - b.id; }); + } + for (var i = 0, l = subs.length; i < l; i++) { + subs[i].update(); + } + }; + + // The current target watcher being evaluated. + // This is globally unique because only one watcher + // can be evaluated at a time. + Dep.target = null; + var targetStack = []; + + function pushTarget (target) { + targetStack.push(target); + Dep.target = target; + } + + function popTarget () { + targetStack.pop(); + Dep.target = targetStack[targetStack.length - 1]; + } + + /* */ + + var VNode = function VNode ( + tag, + data, + children, + text, + elm, + context, + componentOptions, + asyncFactory + ) { + this.tag = tag; + this.data = data; + this.children = children; + this.text = text; + this.elm = elm; + this.ns = undefined; + this.context = context; + this.fnContext = undefined; + this.fnOptions = undefined; + this.fnScopeId = undefined; + this.key = data && data.key; + this.componentOptions = componentOptions; + this.componentInstance = undefined; + this.parent = undefined; + this.raw = false; + this.isStatic = false; + this.isRootInsert = true; + this.isComment = false; + this.isCloned = false; + this.isOnce = false; + this.asyncFactory = asyncFactory; + this.asyncMeta = undefined; + this.isAsyncPlaceholder = false; + }; + + var prototypeAccessors = { child: { configurable: true } }; + + // DEPRECATED: alias for componentInstance for backwards compat. + /* istanbul ignore next */ + prototypeAccessors.child.get = function () { + return this.componentInstance + }; + + Object.defineProperties( VNode.prototype, prototypeAccessors ); + + var createEmptyVNode = function (text) { + if ( text === void 0 ) text = ''; + + var node = new VNode(); + node.text = text; + node.isComment = true; + return node + }; + + function createTextVNode (val) { + return new VNode(undefined, undefined, undefined, String(val)) + } + + // optimized shallow clone + // used for static nodes and slot nodes because they may be reused across + // multiple renders, cloning them avoids errors when DOM manipulations rely + // on their elm reference. + function cloneVNode (vnode) { + var cloned = new VNode( + vnode.tag, + vnode.data, + // #7975 + // clone children array to avoid mutating original in case of cloning + // a child. + vnode.children && vnode.children.slice(), + vnode.text, + vnode.elm, + vnode.context, + vnode.componentOptions, + vnode.asyncFactory + ); + cloned.ns = vnode.ns; + cloned.isStatic = vnode.isStatic; + cloned.key = vnode.key; + cloned.isComment = vnode.isComment; + cloned.fnContext = vnode.fnContext; + cloned.fnOptions = vnode.fnOptions; + cloned.fnScopeId = vnode.fnScopeId; + cloned.asyncMeta = vnode.asyncMeta; + cloned.isCloned = true; + return cloned + } + + /* + * not type checking this file because flow doesn't play well with + * dynamically accessing methods on Array prototype + */ + + var arrayProto = Array.prototype; + var arrayMethods = Object.create(arrayProto); + + var methodsToPatch = [ + 'push', + 'pop', + 'shift', + 'unshift', + 'splice', + 'sort', + 'reverse' + ]; + + /** + * Intercept mutating methods and emit events + */ + methodsToPatch.forEach(function (method) { + // cache original method + var original = arrayProto[method]; + def(arrayMethods, method, function mutator () { + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + + var result = original.apply(this, args); + var ob = this.__ob__; + var inserted; + switch (method) { + case 'push': + case 'unshift': + inserted = args; + break + case 'splice': + inserted = args.slice(2); + break + } + if (inserted) { ob.observeArray(inserted); } + // notify change + ob.dep.notify(); + return result + }); + }); + + /* */ + + var arrayKeys = Object.getOwnPropertyNames(arrayMethods); + + /** + * In some cases we may want to disable observation inside a component's + * update computation. + */ + var shouldObserve = true; + + function toggleObserving (value) { + shouldObserve = value; + } + + /** + * Observer class that is attached to each observed + * object. Once attached, the observer converts the target + * object's property keys into getter/setters that + * collect dependencies and dispatch updates. + */ + var Observer = function Observer (value) { + this.value = value; + this.dep = new Dep(); + this.vmCount = 0; + def(value, '__ob__', this); + if (Array.isArray(value)) { + if (hasProto) { + protoAugment(value, arrayMethods); + } else { + copyAugment(value, arrayMethods, arrayKeys); + } + this.observeArray(value); + } else { + this.walk(value); + } + }; + + /** + * Walk through all properties and convert them into + * getter/setters. This method should only be called when + * value type is Object. + */ + Observer.prototype.walk = function walk (obj) { + var keys = Object.keys(obj); + for (var i = 0; i < keys.length; i++) { + defineReactive$$1(obj, keys[i]); + } + }; + + /** + * Observe a list of Array items. + */ + Observer.prototype.observeArray = function observeArray (items) { + for (var i = 0, l = items.length; i < l; i++) { + observe(items[i]); + } + }; + + // helpers + + /** + * Augment a target Object or Array by intercepting + * the prototype chain using __proto__ + */ + function protoAugment (target, src) { + /* eslint-disable no-proto */ + target.__proto__ = src; + /* eslint-enable no-proto */ + } + + /** + * Augment a target Object or Array by defining + * hidden properties. + */ + /* istanbul ignore next */ + function copyAugment (target, src, keys) { + for (var i = 0, l = keys.length; i < l; i++) { + var key = keys[i]; + def(target, key, src[key]); + } + } + + /** + * Attempt to create an observer instance for a value, + * returns the new observer if successfully observed, + * or the existing observer if the value already has one. + */ + function observe (value, asRootData) { + if (!isObject(value) || value instanceof VNode) { + return + } + var ob; + if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { + ob = value.__ob__; + } else if ( + shouldObserve && + !isServerRendering() && + (Array.isArray(value) || isPlainObject(value)) && + Object.isExtensible(value) && + !value._isVue + ) { + ob = new Observer(value); + } + if (asRootData && ob) { + ob.vmCount++; + } + return ob + } + + /** + * Define a reactive property on an Object. + */ + function defineReactive$$1 ( + obj, + key, + val, + customSetter, + shallow + ) { + var dep = new Dep(); + + var property = Object.getOwnPropertyDescriptor(obj, key); + if (property && property.configurable === false) { + return + } + + // cater for pre-defined getter/setters + var getter = property && property.get; + var setter = property && property.set; + if ((!getter || setter) && arguments.length === 2) { + val = obj[key]; + } + + var childOb = !shallow && observe(val); + Object.defineProperty(obj, key, { + enumerable: true, + configurable: true, + get: function reactiveGetter () { + var value = getter ? getter.call(obj) : val; + if (Dep.target) { + dep.depend(); + if (childOb) { + childOb.dep.depend(); + if (Array.isArray(value)) { + dependArray(value); + } + } + } + return value + }, + set: function reactiveSetter (newVal) { + var value = getter ? getter.call(obj) : val; + /* eslint-disable no-self-compare */ + if (newVal === value || (newVal !== newVal && value !== value)) { + return + } + /* eslint-enable no-self-compare */ + if (customSetter) { + customSetter(); + } + // #7981: for accessor properties without setter + if (getter && !setter) { return } + if (setter) { + setter.call(obj, newVal); + } else { + val = newVal; + } + childOb = !shallow && observe(newVal); + dep.notify(); + } + }); + } + + /** + * Set a property on an object. Adds the new property and + * triggers change notification if the property doesn't + * already exist. + */ + function set (target, key, val) { + if (isUndef(target) || isPrimitive(target) + ) { + warn(("Cannot set reactive property on undefined, null, or primitive value: " + ((target)))); + } + if (Array.isArray(target) && isValidArrayIndex(key)) { + target.length = Math.max(target.length, key); + target.splice(key, 1, val); + return val + } + if (key in target && !(key in Object.prototype)) { + target[key] = val; + return val + } + var ob = (target).__ob__; + if (target._isVue || (ob && ob.vmCount)) { + warn( + 'Avoid adding reactive properties to a Vue instance or its root $data ' + + 'at runtime - declare it upfront in the data option.' + ); + return val + } + if (!ob) { + target[key] = val; + return val + } + defineReactive$$1(ob.value, key, val); + ob.dep.notify(); + return val + } + + /** + * Delete a property and trigger change if necessary. + */ + function del (target, key) { + if (isUndef(target) || isPrimitive(target) + ) { + warn(("Cannot delete reactive property on undefined, null, or primitive value: " + ((target)))); + } + if (Array.isArray(target) && isValidArrayIndex(key)) { + target.splice(key, 1); + return + } + var ob = (target).__ob__; + if (target._isVue || (ob && ob.vmCount)) { + warn( + 'Avoid deleting properties on a Vue instance or its root $data ' + + '- just set it to null.' + ); + return + } + if (!hasOwn(target, key)) { + return + } + delete target[key]; + if (!ob) { + return + } + ob.dep.notify(); + } + + /** + * Collect dependencies on array elements when the array is touched, since + * we cannot intercept array element access like property getters. + */ + function dependArray (value) { + for (var e = (void 0), i = 0, l = value.length; i < l; i++) { + e = value[i]; + e && e.__ob__ && e.__ob__.dep.depend(); + if (Array.isArray(e)) { + dependArray(e); + } + } + } + + /* */ + + /** + * Option overwriting strategies are functions that handle + * how to merge a parent option value and a child option + * value into the final value. + */ + var strats = config.optionMergeStrategies; + + /** + * Options with restrictions + */ + { + strats.el = strats.propsData = function (parent, child, vm, key) { + if (!vm) { + warn( + "option \"" + key + "\" can only be used during instance " + + 'creation with the `new` keyword.' + ); + } + return defaultStrat(parent, child) + }; + } + + /** + * Helper that recursively merges two data objects together. + */ + function mergeData (to, from) { + if (!from) { return to } + var key, toVal, fromVal; + + var keys = hasSymbol + ? Reflect.ownKeys(from) + : Object.keys(from); + + for (var i = 0; i < keys.length; i++) { + key = keys[i]; + // in case the object is already observed... + if (key === '__ob__') { continue } + toVal = to[key]; + fromVal = from[key]; + if (!hasOwn(to, key)) { + set(to, key, fromVal); + } else if ( + toVal !== fromVal && + isPlainObject(toVal) && + isPlainObject(fromVal) + ) { + mergeData(toVal, fromVal); + } + } + return to + } + + /** + * Data + */ + function mergeDataOrFn ( + parentVal, + childVal, + vm + ) { + if (!vm) { + // in a Vue.extend merge, both should be functions + if (!childVal) { + return parentVal + } + if (!parentVal) { + return childVal + } + // when parentVal & childVal are both present, + // we need to return a function that returns the + // merged result of both functions... no need to + // check if parentVal is a function here because + // it has to be a function to pass previous merges. + return function mergedDataFn () { + return mergeData( + typeof childVal === 'function' ? childVal.call(this, this) : childVal, + typeof parentVal === 'function' ? parentVal.call(this, this) : parentVal + ) + } + } else { + return function mergedInstanceDataFn () { + // instance merge + var instanceData = typeof childVal === 'function' + ? childVal.call(vm, vm) + : childVal; + var defaultData = typeof parentVal === 'function' + ? parentVal.call(vm, vm) + : parentVal; + if (instanceData) { + return mergeData(instanceData, defaultData) + } else { + return defaultData + } + } + } + } + + strats.data = function ( + parentVal, + childVal, + vm + ) { + if (!vm) { + if (childVal && typeof childVal !== 'function') { + warn( + 'The "data" option should be a function ' + + 'that returns a per-instance value in component ' + + 'definitions.', + vm + ); + + return parentVal + } + return mergeDataOrFn(parentVal, childVal) + } + + return mergeDataOrFn(parentVal, childVal, vm) + }; + + /** + * Hooks and props are merged as arrays. + */ + function mergeHook ( + parentVal, + childVal + ) { + var res = childVal + ? parentVal + ? parentVal.concat(childVal) + : Array.isArray(childVal) + ? childVal + : [childVal] + : parentVal; + return res + ? dedupeHooks(res) + : res + } + + function dedupeHooks (hooks) { + var res = []; + for (var i = 0; i < hooks.length; i++) { + if (res.indexOf(hooks[i]) === -1) { + res.push(hooks[i]); + } + } + return res + } + + LIFECYCLE_HOOKS.forEach(function (hook) { + strats[hook] = mergeHook; + }); + + /** + * Assets + * + * When a vm is present (instance creation), we need to do + * a three-way merge between constructor options, instance + * options and parent options. + */ + function mergeAssets ( + parentVal, + childVal, + vm, + key + ) { + var res = Object.create(parentVal || null); + if (childVal) { + assertObjectType(key, childVal, vm); + return extend(res, childVal) + } else { + return res + } + } + + ASSET_TYPES.forEach(function (type) { + strats[type + 's'] = mergeAssets; + }); + + /** + * Watchers. + * + * Watchers hashes should not overwrite one + * another, so we merge them as arrays. + */ + strats.watch = function ( + parentVal, + childVal, + vm, + key + ) { + // work around Firefox's Object.prototype.watch... + if (parentVal === nativeWatch) { parentVal = undefined; } + if (childVal === nativeWatch) { childVal = undefined; } + /* istanbul ignore if */ + if (!childVal) { return Object.create(parentVal || null) } + { + assertObjectType(key, childVal, vm); + } + if (!parentVal) { return childVal } + var ret = {}; + extend(ret, parentVal); + for (var key$1 in childVal) { + var parent = ret[key$1]; + var child = childVal[key$1]; + if (parent && !Array.isArray(parent)) { + parent = [parent]; + } + ret[key$1] = parent + ? parent.concat(child) + : Array.isArray(child) ? child : [child]; + } + return ret + }; + + /** + * Other object hashes. + */ + strats.props = + strats.methods = + strats.inject = + strats.computed = function ( + parentVal, + childVal, + vm, + key + ) { + if (childVal && "development" !== 'production') { + assertObjectType(key, childVal, vm); + } + if (!parentVal) { return childVal } + var ret = Object.create(null); + extend(ret, parentVal); + if (childVal) { extend(ret, childVal); } + return ret + }; + strats.provide = mergeDataOrFn; + + /** + * Default strategy. + */ + var defaultStrat = function (parentVal, childVal) { + return childVal === undefined + ? parentVal + : childVal + }; + + /** + * Validate component names + */ + function checkComponents (options) { + for (var key in options.components) { + validateComponentName(key); + } + } + + function validateComponentName (name) { + if (!new RegExp(("^[a-zA-Z][\\-\\.0-9_" + (unicodeRegExp.source) + "]*$")).test(name)) { + warn( + 'Invalid component name: "' + name + '". Component names ' + + 'should conform to valid custom element name in html5 specification.' + ); + } + if (isBuiltInTag(name) || config.isReservedTag(name)) { + warn( + 'Do not use built-in or reserved HTML elements as component ' + + 'id: ' + name + ); + } + } + + /** + * Ensure all props option syntax are normalized into the + * Object-based format. + */ + function normalizeProps (options, vm) { + var props = options.props; + if (!props) { return } + var res = {}; + var i, val, name; + if (Array.isArray(props)) { + i = props.length; + while (i--) { + val = props[i]; + if (typeof val === 'string') { + name = camelize(val); + res[name] = { type: null }; + } else { + warn('props must be strings when using array syntax.'); + } + } + } else if (isPlainObject(props)) { + for (var key in props) { + val = props[key]; + name = camelize(key); + res[name] = isPlainObject(val) + ? val + : { type: val }; + } + } else { + warn( + "Invalid value for option \"props\": expected an Array or an Object, " + + "but got " + (toRawType(props)) + ".", + vm + ); + } + options.props = res; + } + + /** + * Normalize all injections into Object-based format + */ + function normalizeInject (options, vm) { + var inject = options.inject; + if (!inject) { return } + var normalized = options.inject = {}; + if (Array.isArray(inject)) { + for (var i = 0; i < inject.length; i++) { + normalized[inject[i]] = { from: inject[i] }; + } + } else if (isPlainObject(inject)) { + for (var key in inject) { + var val = inject[key]; + normalized[key] = isPlainObject(val) + ? extend({ from: key }, val) + : { from: val }; + } + } else { + warn( + "Invalid value for option \"inject\": expected an Array or an Object, " + + "but got " + (toRawType(inject)) + ".", + vm + ); + } + } + + /** + * Normalize raw function directives into object format. + */ + function normalizeDirectives (options) { + var dirs = options.directives; + if (dirs) { + for (var key in dirs) { + var def$$1 = dirs[key]; + if (typeof def$$1 === 'function') { + dirs[key] = { bind: def$$1, update: def$$1 }; + } + } + } + } + + function assertObjectType (name, value, vm) { + if (!isPlainObject(value)) { + warn( + "Invalid value for option \"" + name + "\": expected an Object, " + + "but got " + (toRawType(value)) + ".", + vm + ); + } + } + + /** + * Merge two option objects into a new one. + * Core utility used in both instantiation and inheritance. + */ + function mergeOptions ( + parent, + child, + vm + ) { + { + checkComponents(child); + } + + if (typeof child === 'function') { + child = child.options; + } + + normalizeProps(child, vm); + normalizeInject(child, vm); + normalizeDirectives(child); + + // Apply extends and mixins on the child options, + // but only if it is a raw options object that isn't + // the result of another mergeOptions call. + // Only merged options has the _base property. + if (!child._base) { + if (child.extends) { + parent = mergeOptions(parent, child.extends, vm); + } + if (child.mixins) { + for (var i = 0, l = child.mixins.length; i < l; i++) { + parent = mergeOptions(parent, child.mixins[i], vm); + } + } + } + + var options = {}; + var key; + for (key in parent) { + mergeField(key); + } + for (key in child) { + if (!hasOwn(parent, key)) { + mergeField(key); + } + } + function mergeField (key) { + var strat = strats[key] || defaultStrat; + options[key] = strat(parent[key], child[key], vm, key); + } + return options + } + + /** + * Resolve an asset. + * This function is used because child instances need access + * to assets defined in its ancestor chain. + */ + function resolveAsset ( + options, + type, + id, + warnMissing + ) { + /* istanbul ignore if */ + if (typeof id !== 'string') { + return + } + var assets = options[type]; + // check local registration variations first + if (hasOwn(assets, id)) { return assets[id] } + var camelizedId = camelize(id); + if (hasOwn(assets, camelizedId)) { return assets[camelizedId] } + var PascalCaseId = capitalize(camelizedId); + if (hasOwn(assets, PascalCaseId)) { return assets[PascalCaseId] } + // fallback to prototype chain + var res = assets[id] || assets[camelizedId] || assets[PascalCaseId]; + if (warnMissing && !res) { + warn( + 'Failed to resolve ' + type.slice(0, -1) + ': ' + id, + options + ); + } + return res + } + + /* */ + + + + function validateProp ( + key, + propOptions, + propsData, + vm + ) { + var prop = propOptions[key]; + var absent = !hasOwn(propsData, key); + var value = propsData[key]; + // boolean casting + var booleanIndex = getTypeIndex(Boolean, prop.type); + if (booleanIndex > -1) { + if (absent && !hasOwn(prop, 'default')) { + value = false; + } else if (value === '' || value === hyphenate(key)) { + // only cast empty string / same name to boolean if + // boolean has higher priority + var stringIndex = getTypeIndex(String, prop.type); + if (stringIndex < 0 || booleanIndex < stringIndex) { + value = true; + } + } + } + // check default value + if (value === undefined) { + value = getPropDefaultValue(vm, prop, key); + // since the default value is a fresh copy, + // make sure to observe it. + var prevShouldObserve = shouldObserve; + toggleObserving(true); + observe(value); + toggleObserving(prevShouldObserve); + } + { + assertProp(prop, key, value, vm, absent); + } + return value + } + + /** + * Get the default value of a prop. + */ + function getPropDefaultValue (vm, prop, key) { + // no default, return undefined + if (!hasOwn(prop, 'default')) { + return undefined + } + var def = prop.default; + // warn against non-factory defaults for Object & Array + if (isObject(def)) { + warn( + 'Invalid default value for prop "' + key + '": ' + + 'Props with type Object/Array must use a factory function ' + + 'to return the default value.', + vm + ); + } + // the raw prop value was also undefined from previous render, + // return previous default value to avoid unnecessary watcher trigger + if (vm && vm.$options.propsData && + vm.$options.propsData[key] === undefined && + vm._props[key] !== undefined + ) { + return vm._props[key] + } + // call factory function for non-Function types + // a value is Function if its prototype is function even across different execution context + return typeof def === 'function' && getType(prop.type) !== 'Function' + ? def.call(vm) + : def + } + + /** + * Assert whether a prop is valid. + */ + function assertProp ( + prop, + name, + value, + vm, + absent + ) { + if (prop.required && absent) { + warn( + 'Missing required prop: "' + name + '"', + vm + ); + return + } + if (value == null && !prop.required) { + return + } + var type = prop.type; + var valid = !type || type === true; + var expectedTypes = []; + if (type) { + if (!Array.isArray(type)) { + type = [type]; + } + for (var i = 0; i < type.length && !valid; i++) { + var assertedType = assertType(value, type[i]); + expectedTypes.push(assertedType.expectedType || ''); + valid = assertedType.valid; + } + } + + if (!valid) { + warn( + getInvalidTypeMessage(name, value, expectedTypes), + vm + ); + return + } + var validator = prop.validator; + if (validator) { + if (!validator(value)) { + warn( + 'Invalid prop: custom validator check failed for prop "' + name + '".', + vm + ); + } + } + } + + var simpleCheckRE = /^(String|Number|Boolean|Function|Symbol)$/; + + function assertType (value, type) { + var valid; + var expectedType = getType(type); + if (simpleCheckRE.test(expectedType)) { + var t = typeof value; + valid = t === expectedType.toLowerCase(); + // for primitive wrapper objects + if (!valid && t === 'object') { + valid = value instanceof type; + } + } else if (expectedType === 'Object') { + valid = isPlainObject(value); + } else if (expectedType === 'Array') { + valid = Array.isArray(value); + } else { + valid = value instanceof type; + } + return { + valid: valid, + expectedType: expectedType + } + } + + /** + * Use function string name to check built-in types, + * because a simple equality check will fail when running + * across different vms / iframes. + */ + function getType (fn) { + var match = fn && fn.toString().match(/^\s*function (\w+)/); + return match ? match[1] : '' + } + + function isSameType (a, b) { + return getType(a) === getType(b) + } + + function getTypeIndex (type, expectedTypes) { + if (!Array.isArray(expectedTypes)) { + return isSameType(expectedTypes, type) ? 0 : -1 + } + for (var i = 0, len = expectedTypes.length; i < len; i++) { + if (isSameType(expectedTypes[i], type)) { + return i + } + } + return -1 + } + + function getInvalidTypeMessage (name, value, expectedTypes) { + var message = "Invalid prop: type check failed for prop \"" + name + "\"." + + " Expected " + (expectedTypes.map(capitalize).join(', ')); + var expectedType = expectedTypes[0]; + var receivedType = toRawType(value); + var expectedValue = styleValue(value, expectedType); + var receivedValue = styleValue(value, receivedType); + // check if we need to specify expected value + if (expectedTypes.length === 1 && + isExplicable(expectedType) && + !isBoolean(expectedType, receivedType)) { + message += " with value " + expectedValue; + } + message += ", got " + receivedType + " "; + // check if we need to specify received value + if (isExplicable(receivedType)) { + message += "with value " + receivedValue + "."; + } + return message + } + + function styleValue (value, type) { + if (type === 'String') { + return ("\"" + value + "\"") + } else if (type === 'Number') { + return ("" + (Number(value))) + } else { + return ("" + value) + } + } + + function isExplicable (value) { + var explicitTypes = ['string', 'number', 'boolean']; + return explicitTypes.some(function (elem) { return value.toLowerCase() === elem; }) + } + + function isBoolean () { + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + + return args.some(function (elem) { return elem.toLowerCase() === 'boolean'; }) + } + + /* */ + + function handleError (err, vm, info) { + // Deactivate deps tracking while processing error handler to avoid possible infinite rendering. + // See: https://github.com/vuejs/vuex/issues/1505 + pushTarget(); + try { + if (vm) { + var cur = vm; + while ((cur = cur.$parent)) { + var hooks = cur.$options.errorCaptured; + if (hooks) { + for (var i = 0; i < hooks.length; i++) { + try { + var capture = hooks[i].call(cur, err, vm, info) === false; + if (capture) { return } + } catch (e) { + globalHandleError(e, cur, 'errorCaptured hook'); + } + } + } + } + } + globalHandleError(err, vm, info); + } finally { + popTarget(); + } + } + + function invokeWithErrorHandling ( + handler, + context, + args, + vm, + info + ) { + var res; + try { + res = args ? handler.apply(context, args) : handler.call(context); + if (res && !res._isVue && isPromise(res) && !res._handled) { + res.catch(function (e) { return handleError(e, vm, info + " (Promise/async)"); }); + // issue #9511 + // avoid catch triggering multiple times when nested calls + res._handled = true; + } + } catch (e) { + handleError(e, vm, info); + } + return res + } + + function globalHandleError (err, vm, info) { + if (config.errorHandler) { + try { + return config.errorHandler.call(null, err, vm, info) + } catch (e) { + // if the user intentionally throws the original error in the handler, + // do not log it twice + if (e !== err) { + logError(e, null, 'config.errorHandler'); + } + } + } + logError(err, vm, info); + } + + function logError (err, vm, info) { + { + warn(("Error in " + info + ": \"" + (err.toString()) + "\""), vm); + } + /* istanbul ignore else */ + if ((inBrowser || inWeex) && typeof console !== 'undefined') { + console.error(err); + } else { + throw err + } + } + + /* */ + + var isUsingMicroTask = false; + + var callbacks = []; + var pending = false; + + function flushCallbacks () { + pending = false; + var copies = callbacks.slice(0); + callbacks.length = 0; + for (var i = 0; i < copies.length; i++) { + copies[i](); + } + } + + // Here we have async deferring wrappers using microtasks. + // In 2.5 we used (macro) tasks (in combination with microtasks). + // However, it has subtle problems when state is changed right before repaint + // (e.g. #6813, out-in transitions). + // Also, using (macro) tasks in event handler would cause some weird behaviors + // that cannot be circumvented (e.g. #7109, #7153, #7546, #7834, #8109). + // So we now use microtasks everywhere, again. + // A major drawback of this tradeoff is that there are some scenarios + // where microtasks have too high a priority and fire in between supposedly + // sequential events (e.g. #4521, #6690, which have workarounds) + // or even between bubbling of the same event (#6566). + var timerFunc; + + // The nextTick behavior leverages the microtask queue, which can be accessed + // via either native Promise.then or MutationObserver. + // MutationObserver has wider support, however it is seriously bugged in + // UIWebView in iOS >= 9.3.3 when triggered in touch event handlers. It + // completely stops working after triggering a few times... so, if native + // Promise is available, we will use it: + /* istanbul ignore next, $flow-disable-line */ + if (typeof Promise !== 'undefined' && isNative(Promise)) { + var p = Promise.resolve(); + timerFunc = function () { + p.then(flushCallbacks); + // In problematic UIWebViews, Promise.then doesn't completely break, but + // it can get stuck in a weird state where callbacks are pushed into the + // microtask queue but the queue isn't being flushed, until the browser + // needs to do some other work, e.g. handle a timer. Therefore we can + // "force" the microtask queue to be flushed by adding an empty timer. + if (isIOS) { setTimeout(noop); } + }; + isUsingMicroTask = true; + } else if (!isIE && typeof MutationObserver !== 'undefined' && ( + isNative(MutationObserver) || + // PhantomJS and iOS 7.x + MutationObserver.toString() === '[object MutationObserverConstructor]' + )) { + // Use MutationObserver where native Promise is not available, + // e.g. PhantomJS, iOS7, Android 4.4 + // (#6466 MutationObserver is unreliable in IE11) + var counter = 1; + var observer = new MutationObserver(flushCallbacks); + var textNode = document.createTextNode(String(counter)); + observer.observe(textNode, { + characterData: true + }); + timerFunc = function () { + counter = (counter + 1) % 2; + textNode.data = String(counter); + }; + isUsingMicroTask = true; + } else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) { + // Fallback to setImmediate. + // Technically it leverages the (macro) task queue, + // but it is still a better choice than setTimeout. + timerFunc = function () { + setImmediate(flushCallbacks); + }; + } else { + // Fallback to setTimeout. + timerFunc = function () { + setTimeout(flushCallbacks, 0); + }; + } + + function nextTick (cb, ctx) { + var _resolve; + callbacks.push(function () { + if (cb) { + try { + cb.call(ctx); + } catch (e) { + handleError(e, ctx, 'nextTick'); + } + } else if (_resolve) { + _resolve(ctx); + } + }); + if (!pending) { + pending = true; + timerFunc(); + } + // $flow-disable-line + if (!cb && typeof Promise !== 'undefined') { + return new Promise(function (resolve) { + _resolve = resolve; + }) + } + } + + /* */ + + var mark; + var measure; + + { + var perf = inBrowser && window.performance; + /* istanbul ignore if */ + if ( + perf && + perf.mark && + perf.measure && + perf.clearMarks && + perf.clearMeasures + ) { + mark = function (tag) { return perf.mark(tag); }; + measure = function (name, startTag, endTag) { + perf.measure(name, startTag, endTag); + perf.clearMarks(startTag); + perf.clearMarks(endTag); + // perf.clearMeasures(name) + }; + } + } + + /* not type checking this file because flow doesn't play well with Proxy */ + + var initProxy; + + { + var allowedGlobals = makeMap( + 'Infinity,undefined,NaN,isFinite,isNaN,' + + 'parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,' + + 'Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,' + + 'require' // for Webpack/Browserify + ); + + var warnNonPresent = function (target, key) { + warn( + "Property or method \"" + key + "\" is not defined on the instance but " + + 'referenced during render. Make sure that this property is reactive, ' + + 'either in the data option, or for class-based components, by ' + + 'initializing the property. ' + + 'See: https://vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.', + target + ); + }; + + var warnReservedPrefix = function (target, key) { + warn( + "Property \"" + key + "\" must be accessed with \"$data." + key + "\" because " + + 'properties starting with "$" or "_" are not proxied in the Vue instance to ' + + 'prevent conflicts with Vue internals. ' + + 'See: https://vuejs.org/v2/api/#data', + target + ); + }; + + var hasProxy = + typeof Proxy !== 'undefined' && isNative(Proxy); + + if (hasProxy) { + var isBuiltInModifier = makeMap('stop,prevent,self,ctrl,shift,alt,meta,exact'); + config.keyCodes = new Proxy(config.keyCodes, { + set: function set (target, key, value) { + if (isBuiltInModifier(key)) { + warn(("Avoid overwriting built-in modifier in config.keyCodes: ." + key)); + return false + } else { + target[key] = value; + return true + } + } + }); + } + + var hasHandler = { + has: function has (target, key) { + var has = key in target; + var isAllowed = allowedGlobals(key) || + (typeof key === 'string' && key.charAt(0) === '_' && !(key in target.$data)); + if (!has && !isAllowed) { + if (key in target.$data) { warnReservedPrefix(target, key); } + else { warnNonPresent(target, key); } + } + return has || !isAllowed + } + }; + + var getHandler = { + get: function get (target, key) { + if (typeof key === 'string' && !(key in target)) { + if (key in target.$data) { warnReservedPrefix(target, key); } + else { warnNonPresent(target, key); } + } + return target[key] + } + }; + + initProxy = function initProxy (vm) { + if (hasProxy) { + // determine which proxy handler to use + var options = vm.$options; + var handlers = options.render && options.render._withStripped + ? getHandler + : hasHandler; + vm._renderProxy = new Proxy(vm, handlers); + } else { + vm._renderProxy = vm; + } + }; + } + + /* */ + + var seenObjects = new _Set(); + + /** + * Recursively traverse an object to evoke all converted + * getters, so that every nested property inside the object + * is collected as a "deep" dependency. + */ + function traverse (val) { + _traverse(val, seenObjects); + seenObjects.clear(); + } + + function _traverse (val, seen) { + var i, keys; + var isA = Array.isArray(val); + if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode) { + return + } + if (val.__ob__) { + var depId = val.__ob__.dep.id; + if (seen.has(depId)) { + return + } + seen.add(depId); + } + if (isA) { + i = val.length; + while (i--) { _traverse(val[i], seen); } + } else { + keys = Object.keys(val); + i = keys.length; + while (i--) { _traverse(val[keys[i]], seen); } + } + } + + /* */ + + var normalizeEvent = cached(function (name) { + var passive = name.charAt(0) === '&'; + name = passive ? name.slice(1) : name; + var once$$1 = name.charAt(0) === '~'; // Prefixed last, checked first + name = once$$1 ? name.slice(1) : name; + var capture = name.charAt(0) === '!'; + name = capture ? name.slice(1) : name; + return { + name: name, + once: once$$1, + capture: capture, + passive: passive + } + }); + + function createFnInvoker (fns, vm) { + function invoker () { + var arguments$1 = arguments; + + var fns = invoker.fns; + if (Array.isArray(fns)) { + var cloned = fns.slice(); + for (var i = 0; i < cloned.length; i++) { + invokeWithErrorHandling(cloned[i], null, arguments$1, vm, "v-on handler"); + } + } else { + // return handler return value for single handlers + return invokeWithErrorHandling(fns, null, arguments, vm, "v-on handler") + } + } + invoker.fns = fns; + return invoker + } + + function updateListeners ( + on, + oldOn, + add, + remove$$1, + createOnceHandler, + vm + ) { + var name, def$$1, cur, old, event; + for (name in on) { + def$$1 = cur = on[name]; + old = oldOn[name]; + event = normalizeEvent(name); + if (isUndef(cur)) { + warn( + "Invalid handler for event \"" + (event.name) + "\": got " + String(cur), + vm + ); + } else if (isUndef(old)) { + if (isUndef(cur.fns)) { + cur = on[name] = createFnInvoker(cur, vm); + } + if (isTrue(event.once)) { + cur = on[name] = createOnceHandler(event.name, cur, event.capture); + } + add(event.name, cur, event.capture, event.passive, event.params); + } else if (cur !== old) { + old.fns = cur; + on[name] = old; + } + } + for (name in oldOn) { + if (isUndef(on[name])) { + event = normalizeEvent(name); + remove$$1(event.name, oldOn[name], event.capture); + } + } + } + + /* */ + + function mergeVNodeHook (def, hookKey, hook) { + if (def instanceof VNode) { + def = def.data.hook || (def.data.hook = {}); + } + var invoker; + var oldHook = def[hookKey]; + + function wrappedHook () { + hook.apply(this, arguments); + // important: remove merged hook to ensure it's called only once + // and prevent memory leak + remove(invoker.fns, wrappedHook); + } + + if (isUndef(oldHook)) { + // no existing hook + invoker = createFnInvoker([wrappedHook]); + } else { + /* istanbul ignore if */ + if (isDef(oldHook.fns) && isTrue(oldHook.merged)) { + // already a merged invoker + invoker = oldHook; + invoker.fns.push(wrappedHook); + } else { + // existing plain hook + invoker = createFnInvoker([oldHook, wrappedHook]); + } + } + + invoker.merged = true; + def[hookKey] = invoker; + } + + /* */ + + function extractPropsFromVNodeData ( + data, + Ctor, + tag + ) { + // we are only extracting raw values here. + // validation and default values are handled in the child + // component itself. + var propOptions = Ctor.options.props; + if (isUndef(propOptions)) { + return + } + var res = {}; + var attrs = data.attrs; + var props = data.props; + if (isDef(attrs) || isDef(props)) { + for (var key in propOptions) { + var altKey = hyphenate(key); + { + var keyInLowerCase = key.toLowerCase(); + if ( + key !== keyInLowerCase && + attrs && hasOwn(attrs, keyInLowerCase) + ) { + tip( + "Prop \"" + keyInLowerCase + "\" is passed to component " + + (formatComponentName(tag || Ctor)) + ", but the declared prop name is" + + " \"" + key + "\". " + + "Note that HTML attributes are case-insensitive and camelCased " + + "props need to use their kebab-case equivalents when using in-DOM " + + "templates. You should probably use \"" + altKey + "\" instead of \"" + key + "\"." + ); + } + } + checkProp(res, props, key, altKey, true) || + checkProp(res, attrs, key, altKey, false); + } + } + return res + } + + function checkProp ( + res, + hash, + key, + altKey, + preserve + ) { + if (isDef(hash)) { + if (hasOwn(hash, key)) { + res[key] = hash[key]; + if (!preserve) { + delete hash[key]; + } + return true + } else if (hasOwn(hash, altKey)) { + res[key] = hash[altKey]; + if (!preserve) { + delete hash[altKey]; + } + return true + } + } + return false + } + + /* */ + + // The template compiler attempts to minimize the need for normalization by + // statically analyzing the template at compile time. + // + // For plain HTML markup, normalization can be completely skipped because the + // generated render function is guaranteed to return Array. There are + // two cases where extra normalization is needed: + + // 1. When the children contains components - because a functional component + // may return an Array instead of a single root. In this case, just a simple + // normalization is needed - if any child is an Array, we flatten the whole + // thing with Array.prototype.concat. It is guaranteed to be only 1-level deep + // because functional components already normalize their own children. + function simpleNormalizeChildren (children) { + for (var i = 0; i < children.length; i++) { + if (Array.isArray(children[i])) { + return Array.prototype.concat.apply([], children) + } + } + return children + } + + // 2. When the children contains constructs that always generated nested Arrays, + // e.g.