├── .editorconfig ├── .github ├── release.yml ├── workflows │ ├── docs.yaml │ ├── integration-tests.yaml │ └── publish.yaml └── xvfb.init ├── .gitignore ├── .tool-versions ├── LICENSE ├── README.md ├── docs ├── .gitignore ├── .textlintrc.json ├── README_EXTENSION.md ├── README_ROOT.md ├── package-lock.json ├── package.json └── prh.yaml ├── imgs ├── codeaction.png └── hover.png ├── package-lock.json ├── package.json ├── packages ├── .eslintrc.base.json ├── test-workspace │ ├── simple │ │ └── README.md │ └── test.code-workspace ├── test │ ├── .textlintignore │ ├── .textlintrc │ ├── .vscode │ │ └── settings.json │ ├── igignorenore.md │ ├── package-lock.json │ ├── package.json │ ├── test.html │ ├── test.md │ ├── testtest.js │ ├── testtest.py │ ├── testtest.tex │ ├── testtest.txt │ └── testtest.vue ├── textlint-server │ ├── .eslintignore │ ├── .eslintrc.json │ ├── .vscode │ │ ├── launch.json │ │ └── settings.json │ ├── package.json │ ├── src │ │ ├── autofix.ts │ │ ├── server.ts │ │ ├── textlint.d.ts │ │ ├── thenable.d.ts │ │ └── types.ts │ └── tsconfig.json └── textlint │ ├── .eslintignore │ ├── .eslintrc.json │ ├── .vscode │ ├── launch.json │ ├── settings.json │ └── tasks.json │ ├── .vscodeignore │ ├── LICENSE.txt │ ├── README.md │ ├── package.json │ ├── src │ ├── extension.ts │ ├── status.ts │ └── types.ts │ ├── test │ ├── extension.test.ts │ ├── index.ts │ ├── runTest.ts │ └── types.ts │ ├── textlint-icon_128x128.png │ └── tsconfig.json ├── vscode-textlint.code-workspace └── webpack.config.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | end_of_line = lf 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [{package.json,circle.yml}] 11 | indent_style = space 12 | indent_size = 2 13 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | changelog: 2 | exclude: 3 | labels: 4 | - 'Type: Meta' 5 | - 'Type: Question' 6 | - 'Type: Release' 7 | 8 | categories: 9 | - title: Security Fixes 10 | labels: ['Type: Security'] 11 | - title: Breaking Changes 12 | labels: ['Type: Breaking Change'] 13 | - title: Features 14 | labels: ['Type: Feature'] 15 | - title: Bug Fixes 16 | labels: ['Type: Bug'] 17 | - title: Documentation 18 | labels: ['Type: Documentation'] 19 | - title: Refactoring 20 | labels: ['Type: Refactoring'] 21 | - title: Testing 22 | labels: ['Type: Testing'] 23 | - title: Maintenance 24 | labels: ['Type: Maintenance'] 25 | - title: CI 26 | labels: ['Type: CI'] 27 | - title: Dependency Updates 28 | labels: ['Type: Dependencies', "dependencies"] 29 | - title: Other Changes 30 | labels: ['*'] 31 | -------------------------------------------------------------------------------- /.github/workflows/docs.yaml: -------------------------------------------------------------------------------- 1 | name: Lint documentation 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | workflow_dispatch: 11 | 12 | jobs: 13 | setup: 14 | runs-on: ubuntu-24.04 15 | steps: 16 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 17 | - uses: jdx/mise-action@5083fe46898c414b2475087cc79da59e7da859e8 # v2.1.11 18 | - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 19 | id: cache-node-modules 20 | with: 21 | path: docs/node_modules 22 | key: ${{ runner.os }}-docs-${{ hashFiles('docs/package-lock.json') }} 23 | - working-directory: docs/ 24 | run: npm ci 25 | 26 | textlint: 27 | needs: setup 28 | runs-on: ubuntu-24.04 29 | steps: 30 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 31 | - uses: jdx/mise-action@5083fe46898c414b2475087cc79da59e7da859e8 # v2.1.11 32 | - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 33 | id: cache-node-modules 34 | with: 35 | path: docs/node_modules 36 | key: ${{ runner.os }}-docs-${{ hashFiles('docs/package-lock.json') }} 37 | - working-directory: docs/ 38 | run: npm run textlint 39 | -------------------------------------------------------------------------------- /.github/workflows/integration-tests.yaml: -------------------------------------------------------------------------------- 1 | name: Integration tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | workflow_dispatch: 11 | 12 | jobs: 13 | windows: 14 | # Disabled due to instability issues 15 | if: false 16 | name: Windows 17 | runs-on: windows-2022 18 | timeout-minutes: 30 19 | steps: 20 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 21 | - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0 22 | with: 23 | node-version-file: .tool-versions 24 | - name: Get npm cache directory 25 | id: npm-cache 26 | run: | 27 | echo "::set-output name=dir::$(npm config get cache)" 28 | - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 29 | with: 30 | path: ${{ steps.npm-cache.outputs.dir }} 31 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} 32 | restore-keys: | 33 | ${{ runner.os }}-node- 34 | - run: npm ci 35 | - run: npm run tsc 36 | - run: npm run build 37 | - run: npm test 38 | linux: 39 | name: Linux 40 | runs-on: ubuntu-24.04 41 | timeout-minutes: 30 42 | env: 43 | DISPLAY: ":99" 44 | steps: 45 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 46 | - run: | 47 | sudo apt-get update 48 | sudo apt-get install -y libxkbfile-dev pkg-config libsecret-1-dev libxss1 dbus dbus-x11 xvfb libgtk-3-0 libgbm1 libasound2t64 libcairo2 49 | # DBus settings 50 | dbus-launch --auto-syntax > /tmp/dbus.env 51 | cat /tmp/dbus.env 52 | # Xvfb settings 53 | sudo Xvfb :99 -screen 0 1024x768x24 -ac +extension GLX +render -noreset > /dev/null 2>&1 & 54 | - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0 55 | with: 56 | node-version-file: .tool-versions 57 | - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 58 | with: 59 | path: ~/.npm 60 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} 61 | restore-keys: | 62 | ${{ runner.os }}-node- 63 | - run: npm ci 64 | - run: npm run tsc 65 | - run: npm run build 66 | - name: Run tests 67 | run: | 68 | source /tmp/dbus.env 69 | npm test 70 | darwin: 71 | # Disabled due to instability issues 72 | if: false 73 | name: macOS 74 | runs-on: macos-14 75 | timeout-minutes: 30 76 | env: 77 | DISPLAY: ":99" 78 | steps: 79 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 80 | - run: | 81 | brew install --cask xquartz 82 | - run: | 83 | /usr/X11/bin/Xvfb :99 -screen 0 1024x768x24 & 84 | sleep 5 85 | - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0 86 | with: 87 | node-version-file: .tool-versions 88 | - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 89 | with: 90 | path: ~/.npm 91 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} 92 | restore-keys: | 93 | ${{ runner.os }}-node- 94 | - run: npm ci 95 | - run: npm run tsc 96 | - run: npm run build 97 | - run: npm test 98 | -------------------------------------------------------------------------------- /.github/workflows/publish.yaml: -------------------------------------------------------------------------------- 1 | name: Publish VS Code extension 2 | 3 | on: 4 | release: 5 | types: [published] 6 | workflow_dispatch: 7 | 8 | jobs: 9 | publish: 10 | name: Publish 11 | runs-on: ubuntu-24.04 12 | steps: 13 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 14 | - name: Setup Node.js 15 | uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0 16 | with: 17 | node-version-file: .tool-versions 18 | - name: Install dependencies 19 | run: npm ci 20 | - name: Package extension 21 | run: npm run package 22 | - name: Publish extension 23 | run: npx vsce publish --packagePath ./packages/textlint/*.vsix --pat ${{ secrets.VSCE_PAT }} 24 | -------------------------------------------------------------------------------- /.github/xvfb.init: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # /etc/rc.d/init.d/xvfbd 4 | # 5 | # chkconfig: 345 95 28 6 | # description: Starts/Stops X Virtual Framebuffer server 7 | # processname: Xvfb 8 | # 9 | ### BEGIN INIT INFO 10 | # Provides: xvfb 11 | # Required-Start: $remote_fs $syslog 12 | # Required-Stop: $remote_fs $syslog 13 | # Default-Start: 2 3 4 5 14 | # Default-Stop: 0 1 6 15 | # Short-Description: Start xvfb at boot time 16 | # Description: Enable xvfb provided by daemon. 17 | ### END INIT INFO 18 | 19 | [ "${NETWORKING}" = "no" ] && exit 0 20 | 21 | PROG="/usr/bin/Xvfb" 22 | PROG_OPTIONS=":10 -ac" 23 | PROG_OUTPUT="/tmp/Xvfb.out" 24 | 25 | case "$1" in 26 | start) 27 | echo "Starting : X Virtual Frame Buffer " 28 | $PROG $PROG_OPTIONS>>$PROG_OUTPUT 2>&1 & 29 | disown -ar 30 | ;; 31 | stop) 32 | echo "Shutting down : X Virtual Frame Buffer" 33 | killproc $PROG 34 | RETVAL=$? 35 | [ $RETVAL -eq 0 ] && /bin/rm -f /var/lock/subsys/Xvfb 36 | /var/run/Xvfb.pid 37 | echo 38 | ;; 39 | restart|reload) 40 | $0 stop 41 | $0 start 42 | RETVAL=$? 43 | ;; 44 | status) 45 | status Xvfb 46 | RETVAL=$? 47 | ;; 48 | *) 49 | echo $"Usage: $0 (start|stop|restart|reload|status)" 50 | exit 1 51 | esac 52 | 53 | exit $RETVAL 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Coverage directory used by tools like istanbul 12 | coverage 13 | 14 | # node-waf configuration 15 | .lock-wscript 16 | 17 | # Compiled binary addons (http://nodejs.org/api/addons.html) 18 | build/Release 19 | 20 | # Dependency directory 21 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git 22 | node_modules 23 | 24 | out 25 | lib 26 | dist 27 | .vscode-test 28 | *.vsix 29 | 30 | -------------------------------------------------------------------------------- /.tool-versions: -------------------------------------------------------------------------------- 1 | nodejs 22.12.0 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2024 3w36zj6 5 | Copyright (c) 2016 taichi 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vscode-textlint 2 | 3 | ![integration-tests](https://github.com/textlint/vscode-textlint/actions/workflows/integration-tests.yaml/badge.svg) 4 | 5 | Extension to integrate [textlint](https://textlint.github.io/) into VS Code. 6 | 7 | ## Installation 8 | 9 | 1. Visit [textlint - Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=3w36zj6.textlint) 10 | 2. Click the "Install" button 11 | 12 | ## Development setup 13 | 14 | 1. Open `vscode-textlint.code-workspace` in VS Code 15 | 2. Run `npm ci` in the **root** folder 16 | 3. Press F5 to build and launch the extension in debug mode 17 | 18 | ## Release process 19 | 20 | 1. Run `npm ci` in the **root** folder 21 | 2. Run `npm run package` in the **root** folder 22 | 3. Run `npx vsce publish --packagePath ./packages/textlint/textlint-*.vsix` in the **root** folder 23 | 24 | ## Acknowledgements 25 | 26 | This project was originally created as [taichi/vscode-textlint](https://github.com/taichi/vscode-textlint). 27 | 28 | Since [v0.12.0](https://github.com/textlint/vscode-textlint/releases/tag/v0.12.0), it has been transferred to the textlint organization for continued maintenance and development. 29 | 30 | We'd like to express our gratitude to [@taichi](https://github.com/taichi) and all contributors. For more details, please see [github.com/orgs/textlint/discussions/2](https://github.com/orgs/textlint/discussions/2). 31 | 32 | The [MIT License](LICENSE) from the upstream repository continues to be honored and upheld. 33 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /docs/.textlintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": {}, 3 | "filters": { 4 | "comments": true 5 | }, 6 | "rules": { 7 | "prh": { 8 | "rulePaths": ["./prh.yaml"] 9 | }, 10 | "rousseau": { 11 | "showLevels": ["warning", "error"], 12 | "ignoreTypes": ["sentence:uppercase", "passive", "weasel", "readibility"] 13 | }, 14 | "unexpanded-acronym": { 15 | "ignore_acronyms": ["SCG", "CLI", "XML", "YAML", "JSON", "TODO", "API", "HTML", "MIT", "FAQ", "VIEW"] 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /docs/README_EXTENSION.md: -------------------------------------------------------------------------------- 1 | ../packages/textlint/README.md -------------------------------------------------------------------------------- /docs/README_ROOT.md: -------------------------------------------------------------------------------- 1 | ../README.md -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vscode-textlint-docs", 3 | "private": true, 4 | "scripts": { 5 | "textlint": "textlint . --format pretty-error --ignore-path .gitignore", 6 | "textlint:fix": "textlint --fix . --ignore-path .gitignore" 7 | }, 8 | "devDependencies": { 9 | "textlint": "^14.6.0", 10 | "textlint-filter-rule-comments": "^1.2.2", 11 | "textlint-rule-prh": "^6.0.0", 12 | "textlint-rule-rousseau": "^1.4.8", 13 | "textlint-rule-unexpanded-acronym": "^1.2.4" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /docs/prh.yaml: -------------------------------------------------------------------------------- 1 | version: 1 2 | rules: 3 | - expected: textlint 4 | options: 5 | wordBoundary: true 6 | prh: textlint is the official spelling. 7 | - expected: VS Code 8 | pattern: /\bvs ?code\b(?!-)/i 9 | prh: VS Code is the official spelling. 10 | specs: 11 | - from: vscode 12 | to: VS Code 13 | - from: vs code 14 | to: VS Code 15 | - from: VsCode 16 | to: VS Code 17 | - from: VSCode 18 | to: VS Code 19 | - from: vscode-textlint 20 | to: vscode-textlint 21 | -------------------------------------------------------------------------------- /imgs/codeaction.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/textlint/vscode-textlint/1b37b1b95d081e03681f454c852be34b78d4139e/imgs/codeaction.png -------------------------------------------------------------------------------- /imgs/hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/textlint/vscode-textlint/1b37b1b95d081e03681f454c852be34b78d4139e/imgs/hover.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vscode-textlint-parent", 3 | "private": true, 4 | "workspaces": [ 5 | "packages/textlint", 6 | "packages/textlint-server" 7 | ], 8 | "scripts": { 9 | "clean": "npm run clean --ws", 10 | "tsc": "npm run tsc --ws", 11 | "build": "webpack --mode production", 12 | "watch": "webpack --mode development --watch", 13 | "test": "npm run test --ws --if-present", 14 | "lint": "npm exec --ws -- eslint --config .eslintrc.json src/**.ts", 15 | "fix": "run-s fix:prettier fix:eslint", 16 | "fix:eslint": "npm exec --ws -- eslint . --ext .ts --fix", 17 | "fix:prettier": "npm exec --ws -- prettier --write . --config ../../package.json --ignore-path ../../.gitignore", 18 | "sort": "npm exec --include-workspace-root --ws -- sort-package-json", 19 | "version": "npm version --ws ", 20 | "upgrade": "npm exec --ws -- ncu -u", 21 | "prepackage": "npm run build", 22 | "package": "npm run package -w packages/textlint" 23 | }, 24 | "devDependencies": { 25 | "@typescript-eslint/eslint-plugin": "^5.2.0", 26 | "@typescript-eslint/parser": "^5.2.0", 27 | "eslint": "^8.1.0", 28 | "eslint-config-prettier": "^8.3.0", 29 | "npm-check-updates": "^11.8.5", 30 | "npm-run-all": "^4.1.5", 31 | "prettier": "^2.4.1", 32 | "sort-package-json": "^1.52.0" 33 | }, 34 | "engines": { 35 | "node": ">=16.0.0" 36 | }, 37 | "prettier": { 38 | "printWidth": 120 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /packages/.eslintrc.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "parserOptions": { 4 | "ecmaVersion": 6, 5 | "sourceType": "module" 6 | }, 7 | "plugins": ["@typescript-eslint"], 8 | "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier"], 9 | "env": { 10 | "node": true 11 | }, 12 | "rules": { 13 | "semi": "off", 14 | "@typescript-eslint/semi": "error", 15 | "no-extra-semi": "warn", 16 | "curly": "warn", 17 | "quotes": ["error", "double", { "allowTemplateLiterals": true }], 18 | "eqeqeq": "error", 19 | "indent": "off", 20 | "@typescript-eslint/no-namespace": "off" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/test-workspace/simple/README.md: -------------------------------------------------------------------------------- 1 | yuo 2 | 3 | yuo 4 | 5 | yuo 6 | 7 | gilr 8 | -------------------------------------------------------------------------------- /packages/test-workspace/test.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "simple" 5 | }, 6 | { 7 | "path": "second" 8 | }, 9 | { 10 | "path": "../test" 11 | } 12 | ], 13 | "settings": { 14 | "textlint.trace": "verbose" 15 | 16 | }, 17 | } 18 | -------------------------------------------------------------------------------- /packages/test/.textlintignore: -------------------------------------------------------------------------------- 1 | igignorenore.md 2 | -------------------------------------------------------------------------------- /packages/test/.textlintrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": { 3 | "html": { 4 | "extensions": [ 5 | ".js" 6 | ] 7 | }, 8 | "latex2e": true 9 | }, 10 | "filters": {}, 11 | "rules": { 12 | "no-todo": true, 13 | "common-misspellings": true, 14 | "preset-ja-technical-writing": true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/test/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "textlint.run": "onType", 3 | "textlint.autoFixOnSave": false, 4 | "textlint.trace": "verbose", 5 | "textlint.languages": [ 6 | "markdown", 7 | "plaintext", 8 | "html", 9 | "tex", 10 | "latex", 11 | "doctex", 12 | "javascript" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /packages/test/igignorenore.md: -------------------------------------------------------------------------------- 1 | [JJUG CCC 2015 Spring(4月11日開催)](http://www.java-users.jp/?page_id=1647) で発表をしてきました。 2 | 3 | 一コマ目であり、エントランスから一番近い入り易い場所だったせいもあるとは思いますが立ち見が出る程の盛況ぶりでした。発表を聞いて下さった皆様、本当にありがとうございます。 4 | 5 | 発表資料はこちらです。 6 | 7 | * [Past & Future of null in Java](https://docs.google.com/presentation/d/1Zb-YYnGewELdsLMAjXZU-5bmI7LxTEm8PNnVUqZl0PI/edit) 8 | 9 | 発表者がどういう風に考えてコンテンツを作り、どういう準備をしているのか、というのは余り共有されていないように思います 10 | 11 | このエントリでは僕がどの様に事前準備を行い、当日はどんな風に考えながら発表していたのか記録しておきます。 12 | 13 | # 事前準備 14 | 15 | ## 内容の決め方 16 | まず、50分では前提条件の多くなる話はできませんので、凡そ言語仕様かライブラリの話をするのが妥当でしょうとアタリを付けます。 17 | 18 | 恐らくビギナー向けを標榜しつつも、設計方法論などメタモデルについて話をするのが良いのでは無いかと考えます。 19 | 20 | Javaの標準ライブラリは表層的な使い方の話をするのは簡単なんですけども、何故そういう設計になっているのかという話は、前提条件が多くなるので望ましく無い、つまり言語仕様について話すという方向性で固まります。 21 | 22 | その頃に、[脱ビギナー!Androidのnullな話](http://techlife.cookpad.com/entry/2015/02/20/195000)が良い感じにバズっていたのを思い出します。 23 | 24 | null の話はビギナー向けとして悪くないし、そこでの考え方や方法論について議論するのであれば、エキスパート同士の会話としても成立するだろうと類推します。 25 | 26 | nullの話をするのであれば、Java8で追加されたOptionalについて話すのが良いでしょう。そう思って手元にあるRSSリーダーからOptionalの記事をガシガシと抜き出します。そこで見つけたのが、今回の発表のネタ元となる [Embracing the Void: 6 Refined Tricks for Dealing with Nulls in Java](https://www.voxxed.com/blog/2015/01/embracing-void-6-refined-tricks-dealing-nulls-java/) です。 27 | 28 | このエントリを繰り返し読んで、自分なりの理解を作ります。[Should java 8 getters return optional type?](http://stackoverflow.com/questions/26327957/should-java-8-getters-return-optional-type/26328555#26328555) でOracleのブライアンによる返答は広く知られるべきものです。 29 | 30 | その時点におけるぼんやりとした僕の理解を箇条書きするとこうなります。 31 | 32 | * Optionalは便利であるけども濫用は避けるべきAPI 33 | * そもそもOptionalって何が便利なんだっけ? 34 | * Optionalを使うモチベーションの話とOptionalに至る話は分けて考えよう 35 | * ScalaのOption型との違いはなんだっけか? 36 | 37 | これで、アウトラインが見えてきましたのでCfPを書きます。 38 | 39 | ## 構成について 40 | 41 | スライドを書き始める前にmarkdownで5000文字程の文章を書いて論理的な構造に根源的な破たんが無いかどうかを簡単に確認します。 42 | 43 | この時点でスライドの中に含めるサンプルコードも併せて書いた上で、コードをmarkdownの中に入れておきます。サンプルコードは**28pt**で表示して画面内に収まるような量にします。ディスプレイだけで見ているとフォントサイズが少々小さくてもコードは読めますが、プロジェクタに映すと小さい文字は基本的に読めません。 44 | 45 | この時点で、サンプルコードの難易度も調整します。コードの量が多いと細かい説明をキチンとしないと伝わらないので、一つのコードでは一つの事だけを表現し、できるだけ余分なものを削ります。 46 | 47 | ## スライドのデザインについて 48 | 49 | 僕にはデザイン能力が欠如しているので、ネタ元となるカッコいいデザインのスライドを探します。今回のスライドで参考にしたスライドはこれらです。 50 | 51 | * http://www.slideshare.net/lafarge777/aea-2013recaphd 52 | * http://noteandpoint.com/2011/06/psfk-presents-future-of-mobile-tagging-report/ 53 | 54 | 今回僕の能力でこれらのスライドから読み取れたのは、 55 | 56 | * 写真をテキトーにボカしてスライドの背景にするとカッケェ 57 | * 半透明のボックスを文字の背景に置くとカッケェし見易い 58 | 59 | 僕のヴィジュアルデザイン能力の欠如が明確に分かってもらえると思います。 60 | 61 | これで、Googlesスライドのテンプレをせっせと作ります。尚、「表示」メニューにある「マスター」を選択するとテンプレを編集できます。 62 | 63 | テンプレをちゃんと作らないとスライドを書く速度を上げられないのでテンプレは作りましょう。 64 | 65 | ## 発表時に進捗が分かるようにする 66 | 67 | スライド一枚辺りの情報量をなるべく揃えると、単純にページ数だけで進捗が分かるので喋り易くなります。 68 | 69 | 一定のテンポでスライドをめくるれるように資料を作ると少々緊張していても、終了時間を適切にコントロールできます。 70 | 71 | 単に右下辺りに分数を出しても良いのですが、背景や文字の色で自分だけに進捗が分かるようにすると、カッコつけられます。 72 | 73 | # 発表開始前 74 | 75 | ## 飲み物について 76 | 普段はウーロン茶を飲んでいるのですけども、大きな声で発表する場合には喉が渇き易いウーロン茶はあまり望ましくありません。 77 | 78 | なので、頭の片隅にあった「リンゴジュースを飲みながらしゃべるとマイクにペチャクチャした音が入り辛い」という謎情報を試してみました。Rebuild.fmか何かで言ってたような気がします。 79 | 80 | これが非常に上手くいったので、発表中に飲むのはリンゴジュースがオススメです。 81 | 82 | ## 10分おきのタイムキーピング 83 | 今回の発表時間は50分でしたが、こういう長時間の発表では時間の管理が非常に難しいので10分おきに時間を連絡して貰いましょう。 84 | 85 | 最後の10分とか20分で時間が分かった所でリカバリはできません。変にリカバリしようとペースを上げたり下げたりすると、テンポがおかしくなるので聞き辛い発表になります。 86 | 87 | ## 声をだす 88 | 89 | 会場に入ると100人以上入る部屋が割り当てられています。つまり、見知らぬ人が大量に見ている場で一時間近く孤軍奮闘する訳です。それなりに場馴れしているとは言え僕だって緊張します。 90 | 91 | そこで僕は、緊張を和らげるために何か理由をつけてマイクを使わずに大きな声で発言するようにしています。今回は前の方の席が余っていたので、それについて何かテキトーな事を喋ることで緊張状態を緩和していました。 92 | 93 | 発表者の皆様におかれましては出来るだけ大きな声で発声しましょう。僕はマイクなしでも100人程度の会場であれば全員に聞こえるレベルの声量でしゃべるようにしています。 94 | 95 | 自信が無かったり、緊張していると声が小さくなりがちですけども、大きな声で喋ると自信が無いことが聴衆に伝わり辛くなります。 96 | 97 | # 発表中 98 | 99 | ## トラブル発生したら、どうする? 100 | 101 | 今回は午前中にA~Dまでが繋がっていた影響で、僕のCD部屋にAB部屋のマイクが入っていました。エンジニア向けのイベントに週末に態々来るような人達は機材トラブルに対しては寛容です。むしろ発表者を応援する気持ちにすらなってくれます。 102 | 103 | こういう時は、少し大げさに腕を振り上げたり、前を歩いたりして大きな声でウケを狙いにいきましょう。皆さん応援する気持ちがありますので、相当変な事を言っても笑って貰えます。今回も、機材トラブルのおかげで始まるなり、凄い一体感があったように思います。 104 | 105 | 変な風に慌てたり、バタつくと見ている側の方がより不安になりますので虚勢を張るくらいで丁度いいです。 106 | 107 | 今回はJJUGのスタッフが迅速に対応してくれたので、数分ロスするだけでトラブルを解消できました。本当にありがたいことです。 108 | 109 | # 終わりに 110 | 発表の未経験者の皆様が発表する際の準備の参考にして頂けると幸いです。 111 | 112 | また、より多くの発表者がより良いコンテンツを提供できるように願っています。 113 | 114 | 115 | -------------------------------------------------------------------------------- /packages/test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "version": "0.0.1", 4 | "description": "", 5 | "private": true, 6 | "dependencies": {}, 7 | "devDependencies": { 8 | "textlint": "^14.2.0", 9 | "textlint-plugin-html": "^1.0.1", 10 | "textlint-plugin-latex2e": "1.2.1", 11 | "textlint-rule-common-misspellings": "^1.0.1", 12 | "textlint-rule-no-todo": "^2.0.1", 13 | "textlint-rule-preset-ja-technical-writing": "^10.0.1" 14 | }, 15 | "scripts": { 16 | "fix": "textlint --fix testtest.txt", 17 | "lint": "textlint testtest.txt" 18 | }, 19 | "author": "taichi", 20 | "license": "MIT" 21 | } 22 | -------------------------------------------------------------------------------- /packages/test/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Title 7 | 8 | 9 | 10 |
11 |

12 | TODO: This is TODO 13 |

14 |
15 | 16 | 17 | -------------------------------------------------------------------------------- /packages/test/test.md: -------------------------------------------------------------------------------- 1 | [JJUG CCC 2015 Spring(4月11日開催)](http://www.java-users.jp/?page_id=1647) で発表をしてきました。 2 | 3 | 一コマ目であり、エントランスから一番近い入り易い場所だったせいもあるとは思いますが立ち見が出る程の盛況ぶりでした。発表を聞いて下さった皆様、本当にありがとうございます。 4 | 5 | 発表資料はこちらです。 6 | 7 | * [Past & Future of null in Java](https://docs.google.com/presentation/d/1Zb-YYnGewELdsLMAjXZU-5bmI7LxTEm8PNnVUqZl0PI/edit) 8 | 9 | 発表者がどういう風に考えてコンテンツを作り、どういう準備をしているのか、というのは余り共有されていないように思います 10 | 11 | このエントリでは僕がどの様に事前準備を行い、当日はどんな風に考えながら発表していたのか記録しておきます。 12 | 13 | # 事前準備 14 | 15 | ## 内容の決め方 16 | まず、50分では前提条件の多くなる話はできませんので、凡そ言語仕様かライブラリの話をするのが妥当でしょうとアタリを付けます。 17 | 18 | 恐らくビギナー向けを標榜しつつも、設計方法論などメタモデルについて話をするのが良いのでは無いかと考えます。 19 | 20 | Javaの標準ライブラリは表層的な使い方の話をするのは簡単なんですけども、何故そういう設計になっているのかという話は、前提条件が多くなるので望ましく無い、つまり言語仕様について話すという方向性で固まります。 21 | 22 | その頃に、[脱ビギナー!Androidのnullな話](http://techlife.cookpad.com/entry/2015/02/20/195000)が良い感じにバズっていたのを思い出します。 23 | 24 | null の話はビギナー向けとして悪くないし、そこでの考え方や方法論について議論するのであれば、エキスパート同士の会話としても成立するだろうと類推します。 25 | 26 | nullの話をするのであれば、Java8で追加されたOptionalについて話すのが良いでしょう。そう思って手元にあるRSSリーダーからOptionalの記事をガシガシと抜き出します。そこで見つけたのが、今回の発表のネタ元となる [Embracing the Void: 6 Refined Tricks for Dealing with Nulls in Java](https://www.voxxed.com/blog/2015/01/embracing-void-6-refined-tricks-dealing-nulls-java/) です。 27 | 28 | このエントリを繰り返し読んで、自分なりの理解を作ります。[Should java 8 getters return optional type?](http://stackoverflow.com/questions/26327957/should-java-8-getters-return-optional-type/26328555#26328555) でOracleのブライアンによる返答は広く知られるべきものです。 29 | 30 | その時点におけるぼんやりとした僕の理解を箇条書きするとこうなります。 31 | 32 | * Optionalは便利であるけども濫用は避けるべきAPI 33 | * そもそもOptionalって何が便利なんだっけ? 34 | * Optionalを使うモチベーションの話とOptionalに至る話は分けて考えよう 35 | * ScalaのOption型との違いはなんだっけか? 36 | 37 | これで、アウトラインが見えてきましたのでCfPを書きます。 38 | 39 | ## 構成について 40 | 41 | スライドを書き始める前にmarkdownで5000文字程の文章を書いて論理的な構造に根源的な破たんが無いかどうかを簡単に確認します。 42 | 43 | この時点でスライドの中に含めるサンプルコードも併せて書いた上で、コードをmarkdownの中に入れておきます。サンプルコードは**28pt**で表示して画面内に収まるような量にします。ディスプレイだけで見ているとフォントサイズが少々小さくてもコードは読めますが、プロジェクタに映すと小さい文字は基本的に読めません。 44 | 45 | この時点で、サンプルコードの難易度も調整します。コードの量が多いと細かい説明をキチンとしないと伝わらないので、一つのコードでは一つの事だけを表現し、できるだけ余分なものを削ります。 46 | 47 | ## スライドのデザインについて 48 | 49 | 僕にはデザイン能力が欠如しているので、ネタ元となるカッコいいデザインのスライドを探します。今回のスライドで参考にしたスライドはこれらです。 50 | 51 | * http://www.slideshare.net/lafarge777/aea-2013recaphd 52 | * http://noteandpoint.com/2011/06/psfk-presents-future-of-mobile-tagging-report/ 53 | 54 | 今回僕の能力でこれらのスライドから読み取れたのは、 55 | 56 | * 写真をテキトーにボカしてスライドの背景にするとカッケェ 57 | * 半透明のボックスを文字の背景に置くとカッケェし見易い 58 | 59 | 僕のヴィジュアルデザイン能力の欠如が明確に分かってもらえると思います。 60 | 61 | これで、Googlesスライドのテンプレをせっせと作ります。尚、「表示」メニューにある「マスター」を選択するとテンプレを編集できます。 62 | 63 | テンプレをちゃんと作らないとスライドを書く速度を上げられないのでテンプレは作りましょう。 64 | 65 | ## 発表時に進捗が分かるようにする 66 | 67 | スライド一枚辺りの情報量をなるべく揃えると、単純にページ数だけで進捗が分かるので喋り易くなります。 68 | 69 | 一定のテンポでスライドをめくるれるように資料を作ると少々緊張していても、終了時間を適切にコントロールできます。 70 | 71 | 単に右下辺りに分数を出しても良いのですが、背景や文字の色で自分だけに進捗が分かるようにすると、カッコつけられます。 72 | 73 | # 発表開始前 74 | 75 | ## 飲み物について 76 | 普段はウーロン茶を飲んでいるのですけども、大きな声で発表する場合には喉が渇き易いウーロン茶はあまり望ましくありません。 77 | 78 | なので、頭の片隅にあった「リンゴジュースを飲みながらしゃべるとマイクにペチャクチャした音が入り辛い」という謎情報を試してみました。Rebuild.fmか何かで言ってたような気がします。 79 | 80 | これが非常に上手くいったので、発表中に飲むのはリンゴジュースがオススメです。 81 | 82 | ## 10分おきのタイムキーピング 83 | 今回の発表時間は50分でしたが、こういう長時間の発表では時間の管理が非常に難しいので10分おきに時間を連絡して貰いましょう。 84 | 85 | 最後の10分とか20分で時間が分かった所でリカバリはできません。変にリカバリしようとペースを上げたり下げたりすると、テンポがおかしくなるので聞き辛い発表になります。 86 | 87 | ## 声をだす 88 | 89 | 会場に入ると100人以上入る部屋が割り当てられています。つまり、見知らぬ人が大量に見ている場で一時間近く孤軍奮闘する訳です。それなりに場馴れしているとは言え僕だって緊張します。 90 | 91 | そこで僕は、緊張を和らげるために何か理由をつけてマイクを使わずに大きな声で発言するようにしています。今回は前の方の席が余っていたので、それについて何かテキトーな事を喋ることで緊張状態を緩和していました。 92 | 93 | 発表者の皆様におかれましては出来るだけ大きな声で発声しましょう。僕はマイクなしでも100人程度の会場であれば全員に聞こえるレベルの声量でしゃべるようにしています。 94 | 95 | 自信が無かったり、緊張していると声が小さくなりがちですけども、大きな声で喋ると自信が無いことが聴衆に伝わり辛くなります。 96 | 97 | # 発表中 98 | 99 | ## トラブル発生したら、どうする? 100 | 101 | 今回は午前中にA~Dまでが繋がっていた影響で、僕のCD部屋にAB部屋のマイクが入っていました。エンジニア向けのイベントに週末に態々来るような人達は機材トラブルに対しては寛容です。むしろ発表者を応援する気持ちにすらなってくれます。 102 | 103 | こういう時は、少し大げさに腕を振り上げたり、前を歩いたりして大きな声でウケを狙いにいきましょう。皆さん応援する気持ちがありますので、相当変な事を言っても笑って貰えます。今回も、機材トラブルのおかげで始まるなり、凄い一体感があったように思います。 104 | 105 | 変な風に慌てたり、バタつくと見ている側の方がより不安になりますので虚勢を張るくらいで丁度いいです。 106 | 107 | 今回はJJUGのスタッフが迅速に対応してくれたので、数分ロスするだけでトラブルを解消できました。本当にありがたいことです。 108 | 109 | # 終わりに 110 | 発表の未経験者の皆様が発表する際の準備の参考にして頂けると幸いです。 111 | 112 | また、より多くの発表者がより良いコンテンツを提供できるように願っています。 113 | 114 | 115 | -------------------------------------------------------------------------------- /packages/test/testtest.js: -------------------------------------------------------------------------------- 1 | // TODO: This is a validated target. 2 | -------------------------------------------------------------------------------- /packages/test/testtest.py: -------------------------------------------------------------------------------- 1 | # TODO: This is NOT a validated target. 2 | -------------------------------------------------------------------------------- /packages/test/testtest.tex: -------------------------------------------------------------------------------- 1 | \\documentclass{article} 2 | \\begin{document} 3 | Yuo have missspeling 4 | \\hoge 5 | I has a pens. 6 | \\end{document} 7 | -------------------------------------------------------------------------------- /packages/test/testtest.txt: -------------------------------------------------------------------------------- 1 | yuo 2 | 3 | yuo 4 | 5 | yuo 6 | 7 | gilr 8 | -------------------------------------------------------------------------------- /packages/test/testtest.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /packages/textlint-server/.eslintignore: -------------------------------------------------------------------------------- 1 | out 2 | node_modules 3 | -------------------------------------------------------------------------------- /packages/textlint-server/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": "../.eslintrc.base.json", 4 | "parserOptions": { 5 | "project": ["./tsconfig.json"] 6 | }, 7 | "rules": { 8 | "no-console": "error" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/textlint-server/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible Node.js debug attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Attach", 9 | "type": "node", 10 | "request": "attach", 11 | "port": 6004, 12 | "sourceMaps": true, 13 | "outDir": "${workspaceRoot}/../textlint/server" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /packages/textlint-server/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | "lib": false 4 | }, 5 | "search.exclude": { 6 | "lib": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/textlint-server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vscode-textlint-server", 3 | "version": "0.12.1", 4 | "description": "Textlint Linter Server", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/textlint/vscode-textlint" 8 | }, 9 | "license": "MIT", 10 | "main": "lib/server.js", 11 | "files": [ 12 | "lib" 13 | ], 14 | "scripts": { 15 | "clean": "rimraf lib", 16 | "tsc": "tsc -p ." 17 | }, 18 | "dependencies": { 19 | "glob": "^7.2.0", 20 | "vscode-languageserver": "^7.0.0", 21 | "vscode-languageserver-textdocument": "1.0.2", 22 | "vscode-uri": "^3.0.2" 23 | }, 24 | "devDependencies": { 25 | "@types/glob": "^7.2.0", 26 | "@types/node": "^16.11.4", 27 | "rimraf": "^3.0.2", 28 | "typescript": "^4.4.4" 29 | }, 30 | "engines": { 31 | "node": "*" 32 | }, 33 | "publishConfig": { 34 | "access": "public" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/textlint-server/src/autofix.ts: -------------------------------------------------------------------------------- 1 | import { Diagnostic } from "vscode-languageserver"; 2 | import { TextDocument } from "vscode-languageserver-textdocument"; 3 | import { TextLintFixCommand, TextLintMessage } from "./textlint"; 4 | export interface AutoFix { 5 | version: number; 6 | ruleId: string; 7 | fix: TextLintFixCommand; 8 | } 9 | export class TextlintFixRepository { 10 | map: Map = new Map(); 11 | 12 | register(doc: TextDocument, diag: Diagnostic, msg: TextLintMessage) { 13 | if (msg.fix && msg.ruleId) { 14 | const fix = { 15 | version: doc.version, 16 | ruleId: msg.ruleId, 17 | fix: msg.fix, 18 | }; 19 | this.map.set(this.toKey(diag), fix); 20 | } 21 | } 22 | 23 | find(diags: Diagnostic[]): AutoFix[] { 24 | return diags.map((d) => this.map.get(this.toKey(d))).filter((af) => af); 25 | } 26 | 27 | clear = () => this.map.clear(); 28 | 29 | toKey(diagnostic: Diagnostic): string { 30 | const range = diagnostic.range; 31 | return `[${range.start.line},${range.start.character},${range.end.line},${range.end.character}]-${diagnostic.code}`; 32 | } 33 | 34 | isEmpty(): boolean { 35 | return this.map.size < 1; 36 | } 37 | 38 | get version(): number { 39 | const af = this.map.values().next().value; 40 | return af ? af.version : -1; 41 | } 42 | 43 | sortedValues(): AutoFix[] { 44 | const a = Array.from(this.map.values()); 45 | return a.sort((left, right) => { 46 | const lr = left.fix.range; 47 | const rr = right.fix.range; 48 | if (lr[0] === rr[0]) { 49 | if (lr[1] === rr[1]) { 50 | return 0; 51 | } 52 | return lr[1] < rr[1] ? -1 : 1; 53 | } 54 | return lr[0] < rr[0] ? -1 : 1; 55 | }); 56 | } 57 | 58 | static overlaps(lastEdit: AutoFix, newEdit: AutoFix): boolean { 59 | return !!lastEdit && lastEdit.fix.range[1] > newEdit.fix.range[0]; 60 | } 61 | 62 | separatedValues(filter: (fix) => boolean = () => true): AutoFix[] { 63 | const sv = this.sortedValues().filter(filter); 64 | if (sv.length < 1) { 65 | return sv; 66 | } 67 | const result: AutoFix[] = []; 68 | result.push(sv[0]); 69 | sv.reduce((prev, cur) => { 70 | if (TextlintFixRepository.overlaps(prev, cur) === false) { 71 | result.push(cur); 72 | return cur; 73 | } 74 | return prev; 75 | }); 76 | return result; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /packages/textlint-server/src/server.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createConnection, 3 | CodeAction, 4 | CodeActionKind, 5 | Command, 6 | Diagnostic, 7 | DiagnosticSeverity, 8 | Position, 9 | Range, 10 | Files, 11 | TextDocuments, 12 | TextEdit, 13 | TextDocumentSyncKind, 14 | ErrorMessageTracker, 15 | ProposedFeatures, 16 | WorkspaceFolder, 17 | } from "vscode-languageserver/node"; 18 | import { TextDocument } from "vscode-languageserver-textdocument"; 19 | 20 | import { Trace, LogTraceNotification } from "vscode-jsonrpc"; 21 | import { URI, Utils as URIUtils } from "vscode-uri"; 22 | 23 | import * as os from "os"; 24 | import * as fs from "fs"; 25 | import * as path from "path"; 26 | import * as glob from "glob"; 27 | import * as minimatch from "minimatch"; 28 | 29 | import { 30 | NoConfigNotification, 31 | NoLibraryNotification, 32 | AllFixesRequest, 33 | StatusNotification, 34 | StartProgressNotification, 35 | StopProgressNotification, 36 | } from "./types"; 37 | 38 | import { TextlintFixRepository, AutoFix } from "./autofix"; 39 | import type { createLinter, TextLintMessage } from "./textlint"; 40 | 41 | const connection = createConnection(ProposedFeatures.all); 42 | const documents = new TextDocuments(TextDocument); 43 | let trace: number; 44 | let settings; 45 | documents.listen(connection); 46 | 47 | type TextlintLinter = { 48 | linter: ReturnType; 49 | availableExtensions: string[]; 50 | }; 51 | 52 | const linterRepo: Map = new Map(); 53 | const fixRepo: Map = new Map(); 54 | 55 | connection.onInitialize(async (params) => { 56 | settings = params.initializationOptions; 57 | trace = Trace.fromString(settings.trace); 58 | return { 59 | capabilities: { 60 | textDocumentSync: TextDocumentSyncKind.Full, 61 | codeActionProvider: true, 62 | workspace: { 63 | workspaceFolders: { 64 | supported: true, 65 | changeNotifications: true, 66 | }, 67 | }, 68 | }, 69 | }; 70 | }); 71 | 72 | connection.onInitialized(async () => { 73 | const folders = await connection.workspace.getWorkspaceFolders(); 74 | await configureEngine(folders); 75 | connection.workspace.onDidChangeWorkspaceFolders(async (event) => { 76 | for (const folder of event.removed) { 77 | linterRepo.delete(folder.uri); 78 | } 79 | await reConfigure(); 80 | }); 81 | }); 82 | 83 | async function configureEngine(folders: WorkspaceFolder[]) { 84 | for (const folder of folders) { 85 | TRACE(`configureEngine ${folder.uri}`); 86 | const root = URI.parse(folder.uri).fsPath; 87 | try { 88 | const configFile = lookupConfig(root); 89 | const ignoreFile = lookupIgnore(root); 90 | 91 | const mod = await resolveModule(root); 92 | const hasLinterAPI = "createLinter" in mod && "loadTextlintrc" in mod; 93 | // textlint v13+ 94 | if (hasLinterAPI) { 95 | const descriptor = await mod.loadTextlintrc({ 96 | configFilePath: configFile, 97 | }); 98 | const linter = mod.createLinter({ 99 | descriptor, 100 | ignoreFilePath: ignoreFile, 101 | }); 102 | linterRepo.set(folder.uri, { 103 | linter, 104 | availableExtensions: descriptor.availableExtensions, 105 | }); 106 | } else { 107 | // TODO: These APIs are deprecated. Remove this code in the future. 108 | // textlint v12 or older - deprecated engingles API 109 | const engine = new mod.TextLintEngine({ 110 | configFile, 111 | ignoreFile, 112 | }); 113 | // polyfill for textlint v12 114 | const linter: ReturnType = { 115 | lintText: (text, filePath) => { 116 | return engine.executeOnText(text, filePath); 117 | }, 118 | lintFiles: (files) => { 119 | return engine.executeOnFiles(files); 120 | }, 121 | fixFiles: (files) => { 122 | return engine.fixFiles(files); 123 | }, 124 | fixText(text, filePath) { 125 | return engine.fixText(text, filePath); 126 | }, 127 | }; 128 | linterRepo.set(folder.uri, { 129 | linter, 130 | availableExtensions: engine.availableExtensions, 131 | }); 132 | } 133 | } catch (e) { 134 | TRACE("failed to configureEngine", e); 135 | } 136 | } 137 | } 138 | 139 | function lookupConfig(root: string): string | undefined { 140 | const roots = [ 141 | candidates(root), 142 | () => { 143 | return fs.existsSync(settings.configPath) ? [settings.configPath] : []; 144 | }, 145 | candidates(os.homedir()), 146 | ]; 147 | for (const fn of roots) { 148 | const files = fn(); 149 | if (0 < files.length) { 150 | return files[0]; 151 | } 152 | } 153 | connection.sendNotification(NoConfigNotification.type, { 154 | workspaceFolder: root, 155 | }); 156 | } 157 | 158 | function lookupIgnore(root: string): string | undefined { 159 | const ignorePath = settings.ignorePath || path.resolve(root, ".textlintignore"); 160 | if (fs.existsSync(ignorePath)) { 161 | return ignorePath; 162 | } 163 | } 164 | 165 | async function resolveModule(root: string) { 166 | try { 167 | TRACE(`Module textlint resolve from ${root}`); 168 | const path = await Files.resolveModulePath(root, "textlint", settings.nodePath, TRACE); 169 | TRACE(`Module textlint got resolved to ${path}`); 170 | return loadModule(path); 171 | } catch (e) { 172 | connection.sendNotification(NoLibraryNotification.type, { 173 | workspaceFolder: root, 174 | }); 175 | throw e; 176 | } 177 | } 178 | 179 | declare const __webpack_require__: typeof require; 180 | declare const __non_webpack_require__: typeof require; 181 | function loadModule(moduleName: string) { 182 | const r = typeof __webpack_require__ === "function" ? __non_webpack_require__ : require; 183 | try { 184 | return r(moduleName); 185 | } catch (err) { 186 | TRACE("load failed", err); 187 | } 188 | return undefined; 189 | } 190 | 191 | async function reConfigure() { 192 | TRACE(`reConfigure`); 193 | await configureEngine(await connection.workspace.getWorkspaceFolders()); 194 | const docs = []; 195 | for (const uri of fixRepo.keys()) { 196 | TRACE(`reConfigure:push ${uri}`); 197 | connection.sendDiagnostics({ uri, diagnostics: [] }); 198 | docs.push(documents.get(uri)); 199 | } 200 | return validateMany(docs); 201 | } 202 | 203 | connection.onDidChangeConfiguration(async (change) => { 204 | const newone = change.settings.textlint; 205 | TRACE(`onDidChangeConfiguration ${JSON.stringify(newone)}`); 206 | settings = newone; 207 | trace = Trace.fromString(newone.trace); 208 | await reConfigure(); 209 | }); 210 | 211 | connection.onDidChangeWatchedFiles(async () => { 212 | TRACE("onDidChangeWatchedFiles"); 213 | await reConfigure(); 214 | }); 215 | 216 | documents.onDidChangeContent(async (event) => { 217 | const uri = event.document.uri; 218 | TRACE(`onDidChangeContent ${uri}`, settings.run); 219 | if (settings.run === "onType") { 220 | return validateSingle(event.document); 221 | } 222 | }); 223 | documents.onDidSave(async (event) => { 224 | const uri = event.document.uri; 225 | TRACE(`onDidSave ${uri}`, settings.run); 226 | if (settings.run === "onSave") { 227 | return validateSingle(event.document); 228 | } 229 | }); 230 | 231 | documents.onDidOpen(async (event) => { 232 | const uri = event.document.uri; 233 | TRACE(`onDidOpen ${uri}`); 234 | if (uri.startsWith("file:") && fixRepo.has(uri) === false) { 235 | fixRepo.set(uri, new TextlintFixRepository()); 236 | return validateSingle(event.document); 237 | } 238 | }); 239 | 240 | function clearDiagnostics(uri) { 241 | TRACE(`clearDiagnostics ${uri}`); 242 | if (uri.startsWith("file:")) { 243 | fixRepo.delete(uri); 244 | connection.sendDiagnostics({ uri, diagnostics: [] }); 245 | } 246 | } 247 | documents.onDidClose((event) => { 248 | const uri = event.document.uri; 249 | TRACE(`onDidClose ${uri}`); 250 | clearDiagnostics(uri); 251 | }); 252 | 253 | async function validateSingle(textDocument: TextDocument) { 254 | sendStartProgress(); 255 | return validate(textDocument) 256 | .then(sendOK, (error) => { 257 | sendError(error); 258 | }) 259 | .then(sendStopProgress); 260 | } 261 | 262 | async function validateMany(textDocuments: TextDocument[]) { 263 | const tracker = new ErrorMessageTracker(); 264 | sendStartProgress(); 265 | for (const doc of textDocuments) { 266 | try { 267 | await validate(doc); 268 | } catch (err) { 269 | tracker.add(err.message); 270 | } 271 | } 272 | tracker.sendErrors(connection); 273 | sendStopProgress(); 274 | } 275 | 276 | function candidates(root: string) { 277 | return () => glob.sync(`${root}/.textlintr{c.js,c.yaml,c.yml,c,c.json}`); 278 | } 279 | 280 | function isTarget(root: string, file: string): boolean { 281 | const relativePath = file.substring(root.length); 282 | return ( 283 | settings.targetPath === "" || 284 | minimatch(relativePath, settings.targetPath, { 285 | matchBase: true, 286 | }) 287 | ); 288 | } 289 | 290 | function startsWith(target, prefix: string): boolean { 291 | if (target.length < prefix.length) { 292 | return false; 293 | } 294 | const tElements = target.split("/"); 295 | const pElements = prefix.split("/"); 296 | for (let i = 0; i < pElements.length; i++) { 297 | if (pElements[i] !== tElements[i]) { 298 | return false; 299 | } 300 | } 301 | 302 | return true; 303 | } 304 | 305 | function lookupEngine(doc: TextDocument): [string, TextlintLinter] { 306 | TRACE(`lookupEngine ${doc.uri}`); 307 | for (const ent of linterRepo.entries()) { 308 | if (startsWith(doc.uri, ent[0])) { 309 | TRACE(`lookupEngine ${doc.uri} => ${ent[0]}`); 310 | return ent; 311 | } 312 | } 313 | TRACE(`lookupEngine ${doc.uri} not found`); 314 | return ["", undefined]; 315 | } 316 | 317 | async function validate(doc: TextDocument) { 318 | TRACE(`validate ${doc.uri}`); 319 | const uri = URI.parse(doc.uri); 320 | if (doc.uri.startsWith("file:") === false) { 321 | TRACE("validation skipped..."); 322 | return; 323 | } 324 | 325 | const repo = fixRepo.get(doc.uri); 326 | if (repo) { 327 | const [folder, engine] = lookupEngine(doc); 328 | const ext = URIUtils.extname(uri); 329 | if (engine && -1 < engine.availableExtensions.findIndex((s) => s === ext) && isTarget(folder, uri.fsPath)) { 330 | repo.clear(); 331 | try { 332 | if (engine.linter.scanFilePath) { 333 | const result = await engine.linter.scanFilePath(uri.fsPath); 334 | if (result.status !== "ok") { 335 | TRACE(`ignore ${doc.uri}`); 336 | return; 337 | } 338 | } 339 | const results = [await engine.linter.lintText(doc.getText(), uri.fsPath)]; 340 | TRACE("results", results); 341 | for (const result of results) { 342 | const diagnostics = result.messages.map(toDiagnostic).map(([msg, diag]) => { 343 | repo.register(doc, diag, msg); 344 | return diag; 345 | }); 346 | TRACE(`sendDiagnostics ${doc.uri}`); 347 | connection.sendDiagnostics({ uri: doc.uri, diagnostics }); 348 | } 349 | } catch (e) { 350 | sendError(e); 351 | } 352 | } 353 | } 354 | } 355 | 356 | function toDiagnosticSeverity(severity?: number): DiagnosticSeverity { 357 | switch (severity) { 358 | case 2: 359 | return DiagnosticSeverity.Error; 360 | case 1: 361 | return DiagnosticSeverity.Warning; 362 | case 0: 363 | return DiagnosticSeverity.Information; 364 | } 365 | return DiagnosticSeverity.Information; 366 | } 367 | 368 | function toDiagnostic(message: TextLintMessage): [TextLintMessage, Diagnostic] { 369 | const txt = message.ruleId ? `${message.message} (${message.ruleId})` : message.message; 370 | const pos_start = Position.create(Math.max(0, message.line - 1), Math.max(0, message.column - 1)); 371 | let offset = 0; 372 | if (message.message.indexOf("->") >= 0) { 373 | offset = message.message.indexOf(" ->"); 374 | } 375 | const quoteIndex = message.message.indexOf(`"`); 376 | if (quoteIndex >= 0) { 377 | offset = Math.max(0, message.message.indexOf(`"`, quoteIndex + 1) - quoteIndex - 1); 378 | } 379 | const pos_end = Position.create(Math.max(0, message.line - 1), Math.max(0, message.column - 1) + offset); 380 | const diag: Diagnostic = { 381 | message: txt, 382 | severity: toDiagnosticSeverity(message.severity), 383 | source: "textlint", 384 | range: Range.create(pos_start, pos_end), 385 | code: message.ruleId, 386 | }; 387 | return [message, diag]; 388 | } 389 | 390 | connection.onCodeAction((params) => { 391 | TRACE("onCodeAction", params); 392 | const result: CodeAction[] = []; 393 | const uri = params.textDocument.uri; 394 | const repo = fixRepo.get(uri); 395 | if (repo && repo.isEmpty() === false) { 396 | const doc = documents.get(uri); 397 | const toAction = (title, edits) => { 398 | const cmd = Command.create(title, "textlint.applyTextEdits", uri, repo.version, edits); 399 | return CodeAction.create(title, cmd, CodeActionKind.QuickFix); 400 | }; 401 | const toTE = (af) => toTextEdit(doc, af); 402 | 403 | repo.find(params.context.diagnostics).forEach((af) => { 404 | result.push(toAction(`Fix this ${af.ruleId} problem`, [toTE(af)])); 405 | const same = repo.separatedValues((v) => v.ruleId === af.ruleId); 406 | if (0 < same.length) { 407 | result.push(toAction(`Fix all ${af.ruleId} problems`, same.map(toTE))); 408 | } 409 | }); 410 | const all = repo.separatedValues(); 411 | if (0 < all.length) { 412 | result.push(toAction(`Fix all auto-fixable problems`, all.map(toTE))); 413 | } 414 | } 415 | return result; 416 | }); 417 | 418 | function toTextEdit(textDocument: TextDocument, af: AutoFix): TextEdit { 419 | return TextEdit.replace( 420 | Range.create(textDocument.positionAt(af.fix.range[0]), textDocument.positionAt(af.fix.range[1])), 421 | af.fix.text || "" 422 | ); 423 | } 424 | 425 | connection.onRequest(AllFixesRequest.type, (params: AllFixesRequest.Params) => { 426 | const uri = params.textDocument.uri; 427 | TRACE(`AllFixesRequest ${uri}`); 428 | const textDocument = documents.get(uri); 429 | const repo = fixRepo.get(uri); 430 | if (repo && repo.isEmpty() === false) { 431 | return { 432 | documentVersion: repo.version, 433 | edits: repo.separatedValues().map((af) => toTextEdit(textDocument, af)), 434 | }; 435 | } 436 | }); 437 | 438 | let inProgress = 0; 439 | function sendStartProgress() { 440 | TRACE(`sendStartProgress ${inProgress}`); 441 | if (inProgress < 1) { 442 | inProgress = 0; 443 | connection.sendNotification(StartProgressNotification.type); 444 | } 445 | inProgress++; 446 | } 447 | 448 | function sendStopProgress() { 449 | TRACE(`sendStopProgress ${inProgress}`); 450 | if (--inProgress < 1) { 451 | inProgress = 0; 452 | connection.sendNotification(StopProgressNotification.type); 453 | } 454 | } 455 | 456 | function sendOK() { 457 | TRACE("sendOK"); 458 | connection.sendNotification(StatusNotification.type, { 459 | status: StatusNotification.Status.OK, 460 | }); 461 | } 462 | function sendError(error) { 463 | TRACE(`sendError ${error}`); 464 | const msg = error.message ? error.message : error; 465 | connection.sendNotification(StatusNotification.type, { 466 | status: StatusNotification.Status.ERROR, 467 | message: msg, 468 | cause: error.stack, 469 | }); 470 | } 471 | 472 | function toVerbose(data?: unknown): string { 473 | let verbose = ""; 474 | if (data) { 475 | verbose = typeof data === "string" ? data : JSON.stringify(data, Object.getOwnPropertyNames(data)); 476 | } 477 | return verbose; 478 | } 479 | 480 | export function TRACE(message: string, data?: unknown) { 481 | switch (trace) { 482 | case Trace.Messages: 483 | connection.sendNotification(LogTraceNotification.type, { 484 | message, 485 | }); 486 | break; 487 | case Trace.Verbose: 488 | connection.sendNotification(LogTraceNotification.type, { 489 | message, 490 | verbose: toVerbose(data), 491 | }); 492 | break; 493 | case Trace.Off: 494 | // do nothing. 495 | break; 496 | default: 497 | break; 498 | } 499 | } 500 | 501 | connection.listen(); 502 | -------------------------------------------------------------------------------- /packages/textlint-server/src/textlint.d.ts: -------------------------------------------------------------------------------- 1 | interface TextLintFixCommand { 2 | text: string; 3 | range: [number, number]; 4 | isAbsolute: boolean; 5 | } 6 | 7 | interface TextLintMessage { 8 | // See src/shared/type/MessageType.js 9 | // Message Type 10 | type: string; 11 | // Rule Id 12 | ruleId: string; 13 | message: string; 14 | // optional data 15 | data?: unknown; 16 | // FixCommand 17 | fix?: TextLintFixCommand; 18 | // location info 19 | // Text -> AST TxtNode(0-based columns) -> textlint -> TextLintMessage(**1-based columns**) 20 | line: number; // start with 1 21 | column: number; // start with 1 22 | // indexed-location 23 | index: number; // start with 0 24 | // Severity Level 25 | // See src/shared/type/SeverityLevel.js 26 | severity?: number; 27 | } 28 | export interface TextlintFixResult { 29 | filePath: string; 30 | // fixed content 31 | output: string; 32 | // all messages = pre-applyingMessages + remainingMessages 33 | // it is same with one of `TextlintResult` 34 | messages: TextLintMessage[]; 35 | // applied fixable messages 36 | applyingMessages: TextLintMessage[]; 37 | // original means original for applyingMessages and remainingMessages 38 | // pre-applyingMessages + remainingMessages 39 | remainingMessages: TextLintMessage[]; 40 | } 41 | 42 | interface TextLintResult { 43 | filePath: string; 44 | messages: TextLintMessage[]; 45 | } 46 | 47 | type ScanFilePathResult = 48 | | { 49 | status: "ok"; 50 | } 51 | | { 52 | status: "ignored"; 53 | } 54 | | { 55 | status: "error"; 56 | }; 57 | 58 | interface TextLintEngine { 59 | availableExtensions: string[]; 60 | 61 | executeOnText(text: string, ext: string): Thenable; 62 | } 63 | type TextlintKernelDescriptor = unknown; 64 | export type CreateLinterOptions = { 65 | descriptor: TextlintKernelDescriptor; 66 | ignoreFilePath?: string; 67 | quiet?: boolean; 68 | cache?: boolean; 69 | cacheLocation?: string; 70 | }; 71 | export type createLinter = (options: CreateLinterOptions) => { 72 | lintFiles(files: string[]): Promise; 73 | lintText(text: string, filePath: string): Promise; 74 | fixFiles(files: string[]): Promise; 75 | fixText(text: string, filePath: string): Promise; 76 | scanFilePath?(filePath: string): Promise; 77 | }; 78 | -------------------------------------------------------------------------------- /packages/textlint-server/src/thenable.d.ts: -------------------------------------------------------------------------------- 1 | /* -------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | * ------------------------------------------------------------------------------------------ */ 5 | // eslint-disable-next-line @typescript-eslint/no-empty-interface 6 | interface Thenable extends PromiseLike {} 7 | -------------------------------------------------------------------------------- /packages/textlint-server/src/types.ts: -------------------------------------------------------------------------------- 1 | import { NotificationType0, NotificationType, RequestType } from "vscode-jsonrpc"; 2 | import { TextDocumentIdentifier, TextEdit } from "vscode-languageserver-types"; 3 | 4 | export namespace ExitNotification { 5 | export interface ExitParams { 6 | code: number; 7 | message: string; 8 | } 9 | export const type = new NotificationType("textlint/exit"); 10 | } 11 | 12 | export namespace StatusNotification { 13 | export enum Status { 14 | OK = 1, 15 | WARN = 2, 16 | ERROR = 3, 17 | } 18 | export interface StatusParams { 19 | status: Status; 20 | message?: string; 21 | cause?: unknown; 22 | } 23 | export const type = new NotificationType("textlint/status"); 24 | } 25 | 26 | export namespace NoConfigNotification { 27 | export const type = new NotificationType("textlint/noconfig"); 28 | 29 | export interface Params { 30 | workspaceFolder: string; 31 | } 32 | } 33 | 34 | export namespace NoLibraryNotification { 35 | export const type = new NotificationType("textlint/nolibrary"); 36 | export interface Params { 37 | workspaceFolder: string; 38 | } 39 | } 40 | 41 | export namespace AllFixesRequest { 42 | export interface Params { 43 | textDocument: TextDocumentIdentifier; 44 | } 45 | 46 | export interface Result { 47 | documentVersion: number; 48 | edits: TextEdit[]; 49 | } 50 | 51 | export const type = new RequestType("textDocument/textlint/allFixes"); 52 | } 53 | 54 | export namespace StartProgressNotification { 55 | export const type = new NotificationType0("textlint/progress/start"); 56 | } 57 | 58 | export namespace StopProgressNotification { 59 | export const type = new NotificationType0("textlint/progress/stop"); 60 | } 61 | -------------------------------------------------------------------------------- /packages/textlint-server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "sourceMap": true, 7 | "outDir": "lib", 8 | "lib": ["es6", "DOM"] 9 | }, 10 | "exclude": ["node_modules", "lib"] 11 | } 12 | -------------------------------------------------------------------------------- /packages/textlint/.eslintignore: -------------------------------------------------------------------------------- 1 | out 2 | node_modules 3 | -------------------------------------------------------------------------------- /packages/textlint/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": "../.eslintrc.base.json", 4 | "parserOptions": { 5 | "project": ["./tsconfig.json"] 6 | }, 7 | "rules": {} 8 | } 9 | -------------------------------------------------------------------------------- /packages/textlint/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | { 3 | "version": "0.2.0", 4 | "configurations": [ 5 | { 6 | "name": "Run Extension", 7 | "type": "extensionHost", 8 | "request": "launch", 9 | "runtimeExecutable": "${execPath}", 10 | "args": ["${workspaceFolder}/../test", "--extensionDevelopmentPath=${workspaceFolder}", "--disable-extensions"], 11 | "outFiles": ["${workspaceFolder}/dist/*.js"], 12 | "stopOnEntry": false, 13 | "sourceMaps": true, 14 | "preLaunchTask": "npm: watch" 15 | }, 16 | { 17 | "name": "Run Extension with workspace", 18 | "type": "extensionHost", 19 | "request": "launch", 20 | "runtimeExecutable": "${execPath}", 21 | "args": [ 22 | "${workspaceFolder}/../test-workspace/test.code-workspace", 23 | "--extensionDevelopmentPath=${workspaceFolder}", 24 | "--disable-extensions" 25 | ], 26 | "outFiles": ["${workspaceFolder}/dist/*.js"], 27 | "stopOnEntry": false, 28 | "sourceMaps": true, 29 | "preLaunchTask": "npm: watch" 30 | }, 31 | { 32 | "type": "node", 33 | "request": "attach", 34 | "name": "Attach to Server", 35 | "address": "localhost", 36 | "protocol": "inspector", 37 | "port": 6011, 38 | "sourceMaps": true, 39 | "outFiles": ["${workspaceFolder}/dist/*.js"] 40 | }, 41 | { 42 | "name": "Test Extension", 43 | "type": "extensionHost", 44 | "request": "launch", 45 | "runtimeExecutable": "${execPath}", 46 | "args": [ 47 | "${workspaceFolder}/../test", 48 | "--extensionDevelopmentPath=${workspaceFolder}", 49 | "--extensionTestsPath=${workspaceFolder}/out/test", 50 | "--disable-extensions" 51 | ], 52 | "outFiles": ["${workspaceFolder}/dist/*.js"], 53 | "preLaunchTask": "npm: pretest" 54 | } 55 | ], 56 | "compounds": [ 57 | { 58 | "name": "Run Extension + Attach Server", 59 | "configurations": ["Run Extension", "Attach to Server"] 60 | } 61 | ] 62 | } 63 | -------------------------------------------------------------------------------- /packages/textlint/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | "out": false 4 | }, 5 | "search.exclude": { 6 | "out": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/textlint/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // Available variables which can be used inside of strings. 2 | // ${workspaceRoot}: the root folder of the team 3 | // ${file}: the current opened file 4 | // ${fileBasename}: the current opened file's basename 5 | // ${fileDirname}: the current opened file's dirname 6 | // ${fileExtname}: the current opened file's extension 7 | // ${cwd}: the current working directory of the spawned process 8 | // A task runner that calls a custom npm script that compiles the extension. 9 | { 10 | "version": "2.0.0", 11 | "tasks": [ 12 | { 13 | "type": "npm", 14 | "script": "tsc", 15 | "group": "build", 16 | "presentation": { 17 | "panel": "dedicated", 18 | "reveal": "never" 19 | }, 20 | "problemMatcher": ["$tsc", "$ts-webpack"] 21 | }, 22 | { 23 | "type": "npm", 24 | "script": "watch", 25 | "isBackground": true, 26 | "group": { 27 | "kind": "build", 28 | "isDefault": true 29 | }, 30 | "presentation": { 31 | "panel": "dedicated", 32 | "reveal": "never" 33 | }, 34 | "problemMatcher": ["$tsc-watch", "$ts-webpack-watch"] 35 | } 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /packages/textlint/.vscodeignore: -------------------------------------------------------------------------------- 1 | **/* 2 | !package.json 3 | !README.md 4 | !LICENSE.txt 5 | !textlint-icon_128x128.png 6 | !dist/*.js 7 | -------------------------------------------------------------------------------- /packages/textlint/LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 3w36zj6 4 | Copyright (c) 2021 sato taichi 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /packages/textlint/README.md: -------------------------------------------------------------------------------- 1 | # VS Code textlint extension 2 | 3 | > ## 📢 IMPORTANT NOTICE 📢 4 | > 5 | > This extension is the community-based maintenance version under the textlint organization. It has been transferred for continued maintenance and development. For more details, please refer to https://github.com/orgs/textlint/discussions/2. 6 | 7 | Integrates [textlint](https://textlint.github.io/) into VS Code. If you are new to textlint, check the [documentation](https://textlint.github.io/). 8 | 9 | ![hover](https://github.com/textlint/vscode-textlint/raw/main/imgs/hover.png?raw=true) 10 | 11 | ![codeaction](https://github.com/textlint/vscode-textlint/raw/main/imgs/codeaction.png?raw=true) 12 | 13 | The extension uses the textlint library installed in the opened workspace folder. If the folder doesn't provide one, the extension looks for a global install version. If you haven't installed textlint either locally or globally, you can do so by running `npm install textlint` in the workspace folder for a local install or `npm install -g textlint` for a global install. 14 | 15 | When working with new projects, you might need to create a `.textlintrc` configuration file. You can do this by either running [`textlint --init`](https://github.com/textlint/textlint/blob/master/docs/getting-started.md#configuration) in a terminal or by using the VS Code command `Create '.textlintrc' file`. 16 | 17 | ## Settings options 18 | 19 | - `textlint.autoFixOnSave` 20 | - Default: `false`. When set to `true`, the extension will automatically fix auto-fixable errors on save. 21 | - `textlint.run` 22 | - Controls when the linter runs. Options: `onSave` or `onType`. Default: `onType`. 23 | - `textlint.nodePath` 24 | - Use this setting if an installed textlint package can't be detected, for example `/myGlobalNodePackages/node_modules`. 25 | - `textlint.trace` 26 | - Traces the communication between VS Code and the textlint linter service. 27 | - `textlint.configPath` 28 | - Absolute path to textlint config file. 29 | - Workspace settings take priority over this setting. 30 | - `textlint.ignorePath` 31 | - Absolute path to textlint ignore file. 32 | - See [here](https://textlint.github.io/docs/ignore.html#ignoring-files-textlintignore) for more information about ignore files. 33 | - `textlint.targetPath` 34 | - Set a glob pattern to determine which files to lint. 35 | - `textlint.languages` 36 | - Languages to lint with textlint. 37 | 38 | ## Commands 39 | 40 | This extension contributes the following commands to the Command palette: 41 | 42 | - Create '.textlintrc' File 43 | - Creates a new `.textlintrc` configuration file in your workspace. 44 | - Fix all auto-fixable Problems 45 | - Applies textlint auto-fix resolutions to all fixable problems in the current document. 46 | 47 | ## Release notes 48 | 49 | ### 0.12.0 50 | 51 | - Changed notification level from `ERROR` to `WARN` when executable file is not found ([#6](https://github.com/textlint/vscode-textlint/pull/6)) 52 | - Thanks to @azu 53 | - Improved `.textlintignore` support using `linter.scanFilePath()` API ([#7](https://github.com/textlint/vscode-textlint/pull/7)) 54 | - Thanks to @frozenbonito 55 | - Enabled to emit activation events on all language files ([#8](https://github.com/textlint/vscode-textlint/pull/8)) 56 | - Thanks to @iku12phycho 57 | 58 | ### 0.11.0 59 | 60 | - Fixed highlight range issue 61 | - Thanks to @Yuiki 62 | 63 | ### 0.10.0 64 | 65 | - Added VS Code workspace support 66 | - Prepared for web-extension 67 | 68 | ### 0.9.0 69 | 70 | - Added `.textlintignore` support 71 | - Thanks to @frozenbonito 72 | 73 | ### 0.8.0 74 | 75 | - Added option to choose languages and improved positioning of highlighted text 76 | - Thanks to @linhtto 77 | 78 | ### 0.7.0 79 | 80 | - Added target path support 81 | - Thanks to @bells17 82 | 83 | ### 0.6.8 84 | 85 | - Changed default value of `textlint.run` to `onSave` 86 | - Added Azure Pipelines for CI testing 87 | 88 | ### 0.6.5 89 | 90 | - Added tex file support including `.tex`, `.latex`, `.doctex` 91 | - This feature works with [LaTeX Workshop](https://marketplace.visualstudio.com/items?itemName=James-Yu.latex-workshop) and [textlint-plugin-latex2e](https://github.com/ta2gch/textlint-plugin-latex2e) 92 | 93 | ### 0.5.0 94 | 95 | - Added `configPath` to configuration. Recommend to use your user settings 96 | 97 | ### 0.4.0 98 | 99 | - Added ability to read configuration file from `HOME` directory 100 | - If you want to use global configuration, you should install textlint and plugins globally 101 | 102 | ### 0.3.0 103 | 104 | - Updated runtime dependencies 105 | 106 | ### 0.2.3 107 | 108 | - Added a tracing option 109 | 110 | ### 0.2.2 111 | 112 | - Fixed various bugs 113 | 114 | ### 0.2.1 115 | 116 | - Added progress notification to StatusBar 117 | 118 | ### 0.2.0 119 | 120 | - Added support for fixing errors 121 | 122 | ### 0.1.0 123 | 124 | - Initial Release 125 | -------------------------------------------------------------------------------- /packages/textlint/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "textlint", 3 | "displayName": "textlint", 4 | "version": "0.12.1", 5 | "description": "Integrates textlint into VS Code.", 6 | "categories": [ 7 | "Linters" 8 | ], 9 | "homepage": "https://github.com/textlint/vscode-textlint", 10 | "bugs": { 11 | "url": "https://github.com/textlint/vscode-textlint/issues" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/textlint/vscode-textlint" 16 | }, 17 | "license": "MIT", 18 | "publisher": "3w36zj6", 19 | "main": "./dist/extension", 20 | "scripts": { 21 | "clean": "rimraf dist out", 22 | "tsc": "tsc -p ./", 23 | "pretest": "npm run tsc", 24 | "test": "node ./out/test/runTest.js", 25 | "package": "vsce package --no-dependencies" 26 | }, 27 | "contributes": { 28 | "commands": [ 29 | { 30 | "title": "Fix all auto-fixable Problems", 31 | "category": "textlint", 32 | "command": "textlint.executeAutofix" 33 | }, 34 | { 35 | "title": "Create '.textlintrc' File", 36 | "category": "textlint", 37 | "command": "textlint.createConfig" 38 | }, 39 | { 40 | "title": "Show Output Channel", 41 | "category": "textlint", 42 | "command": "textlint.showOutputChannel" 43 | } 44 | ], 45 | "configuration": { 46 | "type": "object", 47 | "title": "textlint", 48 | "properties": { 49 | "textlint.languages": { 50 | "default": [ 51 | "markdown", 52 | "plaintext", 53 | "html", 54 | "tex", 55 | "latex", 56 | "doctex" 57 | ], 58 | "type": [ 59 | "array" 60 | ], 61 | "items": { 62 | "type": "string" 63 | }, 64 | "description": "Languages to lint with textlint." 65 | }, 66 | "textlint.configPath": { 67 | "type": "string", 68 | "default": null, 69 | "description": "A absolute path to textlint config file." 70 | }, 71 | "textlint.ignorePath": { 72 | "type": "string", 73 | "default": null, 74 | "description": "A absolute path to textlint ignore file." 75 | }, 76 | "textlint.nodePath": { 77 | "type": "string", 78 | "default": null, 79 | "description": "A path added to NODE_PATH when resolving the textlint module." 80 | }, 81 | "textlint.run": { 82 | "type": "string", 83 | "enum": [ 84 | "onSave", 85 | "onType" 86 | ], 87 | "default": "onSave", 88 | "description": "Run the linter on save (onSave) or on type (onType)" 89 | }, 90 | "textlint.autoFixOnSave": { 91 | "type": "boolean", 92 | "default": false, 93 | "description": "Turns auto fix on save on or off." 94 | }, 95 | "textlint.trace": { 96 | "type": "string", 97 | "enum": [ 98 | "off", 99 | "messages", 100 | "verbose" 101 | ], 102 | "default": "off", 103 | "description": "Traces the communication between VSCode and the textlint linter service." 104 | }, 105 | "textlint.targetPath": { 106 | "type": "string", 107 | "default": "", 108 | "description": "Target files path that runs lint." 109 | } 110 | } 111 | } 112 | }, 113 | "activationEvents": [ 114 | "onLanguage", 115 | "onCommand:textlint.showOutputChannel", 116 | "onCommand:textlint.createConfig", 117 | "onCommand:textlint.executeAutofix" 118 | ], 119 | "dependencies": { 120 | "minimatch": "^3.0.4", 121 | "vscode-languageclient": "^7.0.0", 122 | "vscode-uri": "^3.0.2" 123 | }, 124 | "devDependencies": { 125 | "@types/fs-extra": "9.0.13", 126 | "@types/mocha": "^9.0.0", 127 | "@types/node": "^16.11.4", 128 | "@types/vscode": "^1.61.0", 129 | "@vscode/test-electron": "^2.4.1", 130 | "@vscode/vsce": "^3.2.2", 131 | "fs-extra": "^10.0.0", 132 | "merge-options": "^3.0.4", 133 | "mocha": "^9.1.3", 134 | "npm-run-all": "^4.1.5", 135 | "rimraf": "^3.0.2", 136 | "ts-loader": "^9.2.6", 137 | "typescript": "^4.4.4", 138 | "webpack": "^5.61.0", 139 | "webpack-cli": "^4.9.1" 140 | }, 141 | "engines": { 142 | "vscode": "^1.74.0" 143 | }, 144 | "icon": "textlint-icon_128x128.png", 145 | "galleryBanner": { 146 | "color": "#5acbe3", 147 | "theme": "light" 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /packages/textlint/src/extension.ts: -------------------------------------------------------------------------------- 1 | import * as minimatch from "minimatch"; 2 | 3 | import { 4 | workspace, 5 | window, 6 | commands, 7 | ExtensionContext, 8 | Disposable, 9 | QuickPickItem, 10 | WorkspaceFolder, 11 | TextDocumentSaveReason, 12 | TextEditor, 13 | } from "vscode"; 14 | 15 | import { 16 | TextEdit, 17 | State as ServerState, 18 | ErrorHandler, 19 | ErrorAction, 20 | CloseAction, 21 | RevealOutputChannelOn, 22 | } from "vscode-languageclient"; 23 | 24 | import { LanguageClient, LanguageClientOptions, ServerOptions, TransportKind } from "vscode-languageclient/node"; 25 | 26 | import { LogTraceNotification } from "vscode-jsonrpc"; 27 | 28 | import { Utils as URIUtils } from "vscode-uri"; 29 | 30 | import { 31 | StatusNotification, 32 | NoConfigNotification, 33 | NoLibraryNotification, 34 | ExitNotification, 35 | AllFixesRequest, 36 | StartProgressNotification, 37 | StopProgressNotification, 38 | } from "./types"; 39 | 40 | import { Status, StatusBar } from "./status"; 41 | 42 | export interface ExtensionInternal { 43 | client: LanguageClient; 44 | statusBar: StatusBar; 45 | onAllFixesComplete(fn: (te: TextEditor, edits: TextEdit[], ok: boolean) => void); 46 | } 47 | 48 | export function activate(context: ExtensionContext): ExtensionInternal { 49 | const client = newClient(context); 50 | const statusBar = new StatusBar(getConfig("languages")); 51 | client.onReady().then(() => { 52 | client.onDidChangeState((event) => { 53 | statusBar.serverRunning = event.newState === ServerState.Running; 54 | }); 55 | client.onNotification(StatusNotification.type, (p: StatusNotification.StatusParams) => { 56 | statusBar.status = to(p.status); 57 | if (p.message || p.cause) { 58 | statusBar.status.log(client, p.message, p.cause); 59 | } 60 | }); 61 | client.onNotification(NoConfigNotification.type, (p) => { 62 | statusBar.status = Status.WARN; 63 | statusBar.status.log( 64 | client, 65 | `No textlint configuration (e.g .textlintrc) found in ${p.workspaceFolder} . 66 | File will not be validated. Consider running the 'Create .textlintrc file' command.` 67 | ); 68 | }); 69 | client.onNotification(NoLibraryNotification.type, (p) => { 70 | statusBar.status = Status.WARN; 71 | statusBar.status.log( 72 | client, 73 | `Failed to load the textlint library in ${p.workspaceFolder} . 74 | To use textlint in this workspace please install textlint using 'npm install textlint' or globally using 'npm install -g textlint'. 75 | You need to reopen the workspace after installing textlint.` 76 | ); 77 | }); 78 | client.onNotification(StartProgressNotification.type, () => statusBar.startProgress()); 79 | client.onNotification(StopProgressNotification.type, () => statusBar.stopProgress()); 80 | 81 | client.onNotification(LogTraceNotification.type, (p) => client.info(p.message, p.verbose)); 82 | const changeConfigHandler = () => configureAutoFixOnSave(client); 83 | workspace.onDidChangeConfiguration(changeConfigHandler); 84 | changeConfigHandler(); 85 | }); 86 | context.subscriptions.push( 87 | commands.registerCommand("textlint.createConfig", createConfig), 88 | commands.registerCommand("textlint.applyTextEdits", makeApplyFixFn(client)), 89 | commands.registerCommand("textlint.executeAutofix", makeAutoFixFn(client)), 90 | commands.registerCommand("textlint.showOutputChannel", () => client.outputChannel.show()), 91 | client.start(), 92 | statusBar 93 | ); 94 | // for testing purpose 95 | return { 96 | client, 97 | statusBar, 98 | onAllFixesComplete, 99 | }; 100 | } 101 | 102 | function newClient(context: ExtensionContext): LanguageClient { 103 | const module = URIUtils.joinPath(context.extensionUri, "dist", "server.js").fsPath; 104 | const debugOptions = { execArgv: ["--nolazy", "--inspect=6011"] }; 105 | 106 | const serverOptions: ServerOptions = { 107 | run: { module, transport: TransportKind.ipc }, 108 | debug: { module, transport: TransportKind.ipc, options: debugOptions }, 109 | }; 110 | 111 | // eslint-disable-next-line prefer-const 112 | let defaultErrorHandler: ErrorHandler; 113 | let serverCalledProcessExit = false; 114 | const clientOptions: LanguageClientOptions = { 115 | documentSelector: getConfig("languages").map((id) => { 116 | return { language: id, scheme: "file" }; 117 | }), 118 | diagnosticCollectionName: "textlint", 119 | revealOutputChannelOn: RevealOutputChannelOn.Error, 120 | synchronize: { 121 | configurationSection: "textlint", 122 | fileEvents: [ 123 | workspace.createFileSystemWatcher("**/package.json"), 124 | workspace.createFileSystemWatcher("**/.textlintrc"), 125 | workspace.createFileSystemWatcher("**/.textlintrc.{js,json,yml,yaml}"), 126 | workspace.createFileSystemWatcher("**/.textlintignore"), 127 | ], 128 | }, 129 | initializationOptions: () => { 130 | return { 131 | configPath: getConfig("configPath"), 132 | ignorePath: getConfig("ignorePath"), 133 | nodePath: getConfig("nodePath"), 134 | run: getConfig("run"), 135 | trace: getConfig("trace", "off"), 136 | }; 137 | }, 138 | initializationFailedHandler: (error) => { 139 | client.error("Server initialization failed.", error); 140 | return false; 141 | }, 142 | errorHandler: { 143 | error: (error, message, count): ErrorAction => { 144 | return defaultErrorHandler.error(error, message, count); 145 | }, 146 | closed: (): CloseAction => { 147 | if (serverCalledProcessExit) { 148 | return CloseAction.DoNotRestart; 149 | } 150 | return defaultErrorHandler.closed(); 151 | }, 152 | }, 153 | }; 154 | 155 | const client = new LanguageClient("textlint", serverOptions, clientOptions); 156 | defaultErrorHandler = client.createDefaultErrorHandler(); 157 | client.onReady().then(() => { 158 | client.onNotification(ExitNotification.type, () => { 159 | serverCalledProcessExit = true; 160 | }); 161 | }); 162 | return client; 163 | } 164 | 165 | async function createConfig() { 166 | const folders = workspace.workspaceFolders; 167 | if (!folders) { 168 | await window.showErrorMessage( 169 | "An textlint configuration can only be generated if VS Code is opened on a workspace folder." 170 | ); 171 | return; 172 | } 173 | 174 | const noConfigs = await filterNoConfigFolders(folders); 175 | 176 | if (noConfigs.length < 1 && 0 < folders.length) { 177 | await window.showErrorMessage("textlint configuration file already exists in this workspace."); 178 | return; 179 | } 180 | 181 | if (noConfigs.length === 1) { 182 | await emitConfig(noConfigs[0]); 183 | } else { 184 | const item = await window.showQuickPick(toQuickPickItems(noConfigs)); 185 | if (item) { 186 | await emitConfig(item.folder); 187 | } 188 | } 189 | } 190 | 191 | async function filterNoConfigFolders(folders: readonly WorkspaceFolder[]): Promise { 192 | const result = []; 193 | outer: for (const folder of folders) { 194 | const candidates = ["", ".js", ".yaml", ".yml", ".json"].map((ext) => 195 | URIUtils.joinPath(folder.uri, ".textlintrc" + ext) 196 | ); 197 | for (const configPath of candidates) { 198 | try { 199 | await workspace.fs.stat(configPath); 200 | continue outer; 201 | // eslint-disable-next-line no-empty 202 | } catch {} 203 | } 204 | result.push(folder); 205 | } 206 | return result; 207 | } 208 | 209 | async function emitConfig(folder: WorkspaceFolder) { 210 | if (folder) { 211 | await workspace.fs.writeFile( 212 | URIUtils.joinPath(folder.uri, ".textlintrc"), 213 | Buffer.from( 214 | `{ 215 | "filters": {}, 216 | "rules": {} 217 | }`, 218 | "utf8" 219 | ) 220 | ); 221 | } 222 | } 223 | 224 | function toQuickPickItems(folders: WorkspaceFolder[]): ({ folder: WorkspaceFolder } & QuickPickItem)[] { 225 | return folders.map((folder) => { 226 | return { 227 | label: folder.name, 228 | description: folder.uri.path, 229 | folder, 230 | }; 231 | }); 232 | } 233 | 234 | let autoFixOnSave: Disposable; 235 | 236 | function configureAutoFixOnSave(client: LanguageClient) { 237 | const auto = getConfig("autoFixOnSave", false); 238 | disposeAutoFixOnSave(); 239 | 240 | if (auto) { 241 | const languages = new Set(getConfig("languages", [])); 242 | autoFixOnSave = workspace.onWillSaveTextDocument((event) => { 243 | const doc = event.document; 244 | const target = getConfig("targetPath", null); 245 | if ( 246 | languages.has(doc.languageId) && 247 | event.reason !== TextDocumentSaveReason.AfterDelay && 248 | (target === "" || 249 | minimatch(workspace.asRelativePath(doc.uri), target, { 250 | matchBase: true, 251 | })) 252 | ) { 253 | const version = doc.version; 254 | const uri: string = doc.uri.toString(); 255 | event.waitUntil( 256 | client.sendRequest(AllFixesRequest.type, { textDocument: { uri } }).then((result: AllFixesRequest.Result) => { 257 | return result && result.documentVersion === version 258 | ? client.protocol2CodeConverter.asTextEdits(result.edits) 259 | : []; 260 | }) 261 | ); 262 | } 263 | }); 264 | } 265 | } 266 | 267 | function disposeAutoFixOnSave() { 268 | if (autoFixOnSave) { 269 | autoFixOnSave.dispose(); 270 | autoFixOnSave = undefined; 271 | } 272 | } 273 | 274 | function makeAutoFixFn(client: LanguageClient) { 275 | return () => { 276 | const textEditor = window.activeTextEditor; 277 | if (textEditor) { 278 | const uri: string = textEditor.document.uri.toString(); 279 | client.sendRequest(AllFixesRequest.type, { textDocument: { uri } }).then( 280 | async (result: AllFixesRequest.Result) => { 281 | if (result) { 282 | await applyTextEdits(client, uri, result.documentVersion, result.edits); 283 | } 284 | }, 285 | (error) => { 286 | client.error("Failed to apply textlint fixes to the document.", error); 287 | } 288 | ); 289 | } 290 | }; 291 | } 292 | 293 | function makeApplyFixFn(client: LanguageClient) { 294 | return async (uri: string, documentVersion: number, edits: TextEdit[]) => { 295 | await applyTextEdits(client, uri, documentVersion, edits); 296 | }; 297 | } 298 | 299 | const allFixesCompletes = []; 300 | function onAllFixesComplete(fn: (te: TextEditor, edits: TextEdit[], ok: boolean) => void) { 301 | allFixesCompletes.push(fn); 302 | } 303 | 304 | async function applyTextEdits( 305 | client: LanguageClient, 306 | uri: string, 307 | documentVersion: number, 308 | edits: TextEdit[] 309 | ): Promise { 310 | const textEditor = window.activeTextEditor; 311 | if (textEditor && textEditor.document.uri.toString() === uri) { 312 | if (textEditor.document.version === documentVersion) { 313 | return textEditor 314 | .edit((mutator) => { 315 | edits.forEach((ed) => mutator.replace(client.protocol2CodeConverter.asRange(ed.range), ed.newText)); 316 | }) 317 | .then( 318 | (ok) => { 319 | client.info("AllFixesComplete"); 320 | allFixesCompletes.forEach((fn) => fn(textEditor, edits, ok)); 321 | return true; 322 | }, 323 | (errors) => { 324 | client.error(errors.message, errors.stack); 325 | } 326 | ); 327 | } else { 328 | window.showInformationMessage(`textlint fixes are outdated and can't be applied to ${uri}`); 329 | return true; 330 | } 331 | } 332 | } 333 | 334 | export function deactivate() { 335 | disposeAutoFixOnSave(); 336 | } 337 | 338 | function config() { 339 | return workspace.getConfiguration("textlint"); 340 | } 341 | 342 | function getConfig(section: string, defaults?: T) { 343 | return config().get(section, defaults); 344 | } 345 | 346 | function to(status: StatusNotification.Status): Status { 347 | switch (status) { 348 | case StatusNotification.Status.OK: 349 | return Status.OK; 350 | case StatusNotification.Status.WARN: 351 | return Status.WARN; 352 | case StatusNotification.Status.ERROR: 353 | return Status.ERROR; 354 | default: 355 | return Status.ERROR; 356 | } 357 | } 358 | -------------------------------------------------------------------------------- /packages/textlint/src/status.ts: -------------------------------------------------------------------------------- 1 | import { window, StatusBarAlignment, TextEditor } from "vscode"; 2 | 3 | export interface Status { 4 | label: string; 5 | color: string; 6 | log: ( 7 | logger: { 8 | info(message: string, data?: unknown): void; 9 | warn(message: string, data?: unknown): void; 10 | error(message: string, data?: unknown): void; 11 | }, 12 | msg: string, 13 | data?: unknown 14 | ) => void; 15 | } 16 | 17 | export namespace Status { 18 | export const OK: Status = { 19 | label: "textlint", 20 | color: "", 21 | log: (logger, msg, data?) => logger.info(msg, data), 22 | }; 23 | export const WARN: Status = { 24 | label: "textlint: Warning", 25 | color: "yellow", 26 | log: (logger, msg, data?) => logger.warn(msg, data), 27 | }; 28 | export const ERROR: Status = { 29 | label: "textlint: Error", 30 | color: "darkred", 31 | log: (logger, msg, data?) => logger.error(msg, data), 32 | }; 33 | } 34 | 35 | export class StatusBar { 36 | private _delegate = window.createStatusBarItem(StatusBarAlignment.Right, 0); 37 | private _supports: string[]; 38 | private _status = Status.OK; 39 | private _serverRunning = false; 40 | private _intervalToken; 41 | constructor(supports) { 42 | this._supports = supports; 43 | this._delegate.text = this._status.label; 44 | window.onDidChangeActiveTextEditor((te) => this.updateWith(te)); 45 | this.update(); 46 | } 47 | 48 | dispose() { 49 | this.stopProgress(); 50 | this._delegate.dispose(); 51 | } 52 | 53 | show(show: boolean) { 54 | if (show) { 55 | this._delegate.show(); 56 | } else { 57 | this._delegate.hide(); 58 | } 59 | } 60 | activate(languageId: string) { 61 | if (languageId === "") { 62 | return; 63 | } 64 | 65 | if (this._supports.includes(languageId)) { 66 | this._delegate.color = ""; 67 | this._delegate.tooltip = 68 | "need to restart this extension or check this extension setting and .textlintrc if textlint is not working."; 69 | } else { 70 | this._delegate.color = "#818589"; 71 | this._delegate.tooltip = `textlint is inactive on ${languageId}.`; 72 | } 73 | } 74 | 75 | get status(): Status { 76 | return this._status; 77 | } 78 | 79 | set status(s: Status) { 80 | this._status = s; 81 | this.update(); 82 | } 83 | 84 | get serverRunning(): boolean { 85 | return this._serverRunning; 86 | } 87 | 88 | set serverRunning(sr: boolean) { 89 | this._serverRunning = sr; 90 | window.showInformationMessage(sr ? "textlint server is running." : "textlint server stopped."); 91 | this.update(); 92 | } 93 | 94 | update() { 95 | this.updateWith(window.activeTextEditor); 96 | } 97 | 98 | updateWith(editor: TextEditor) { 99 | this._delegate.text = this.status.label; 100 | const languageId = editor?.document.languageId ?? ""; 101 | this.activate(languageId); 102 | 103 | const shouldShowStatusBar = 104 | !this.serverRunning || this._status !== Status.OK || this._supports.includes(languageId); 105 | this.show(shouldShowStatusBar); 106 | } 107 | 108 | startProgress() { 109 | if (!this._intervalToken) { 110 | let c = 0; 111 | const orig = this._delegate.text; 112 | const chars = "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏"; 113 | const l = chars.length; 114 | this._intervalToken = setInterval(() => { 115 | const t = c++ % l; 116 | this._delegate.text = chars[t] + " " + orig; 117 | }, 300); 118 | } 119 | } 120 | 121 | stopProgress() { 122 | if (this._intervalToken) { 123 | const tk = this._intervalToken; 124 | this._intervalToken = null; 125 | clearInterval(tk); 126 | this.update(); 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /packages/textlint/src/types.ts: -------------------------------------------------------------------------------- 1 | import { NotificationType0, NotificationType, RequestType } from "vscode-jsonrpc"; 2 | import { TextDocumentIdentifier, TextEdit } from "vscode-languageserver-types"; 3 | 4 | export namespace ExitNotification { 5 | export interface ExitParams { 6 | code: number; 7 | message: string; 8 | } 9 | export const type = new NotificationType("textlint/exit"); 10 | } 11 | 12 | export namespace StatusNotification { 13 | export enum Status { 14 | OK = 1, 15 | WARN = 2, 16 | ERROR = 3, 17 | } 18 | export interface StatusParams { 19 | status: Status; 20 | message?: string; 21 | cause?: unknown; 22 | } 23 | export const type = new NotificationType("textlint/status"); 24 | } 25 | 26 | export namespace NoConfigNotification { 27 | export const type = new NotificationType("textlint/noconfig"); 28 | 29 | export interface Params { 30 | workspaceFolder: string; 31 | } 32 | } 33 | 34 | export namespace NoLibraryNotification { 35 | export const type = new NotificationType("textlint/nolibrary"); 36 | export interface Params { 37 | workspaceFolder: string; 38 | } 39 | } 40 | 41 | export namespace AllFixesRequest { 42 | export interface Params { 43 | textDocument: TextDocumentIdentifier; 44 | } 45 | 46 | export interface Result { 47 | documentVersion: number; 48 | edits: TextEdit[]; 49 | } 50 | 51 | export const type = new RequestType("textDocument/textlint/allFixes"); 52 | } 53 | 54 | export namespace StartProgressNotification { 55 | export const type = new NotificationType0("textlint/progress/start"); 56 | } 57 | 58 | export namespace StopProgressNotification { 59 | export const type = new NotificationType0("textlint/progress/stop"); 60 | } 61 | -------------------------------------------------------------------------------- /packages/textlint/test/extension.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from "assert"; 2 | import * as fs from "fs-extra"; 3 | import * as path from "path"; 4 | 5 | import { 6 | workspace, 7 | window, 8 | commands, 9 | Extension, 10 | extensions, 11 | Disposable, 12 | Uri, 13 | Position, 14 | WorkspaceEdit, 15 | Diagnostic, 16 | DiagnosticSeverity, 17 | } from "vscode"; 18 | import { ExtensionInternal } from "../src/extension"; 19 | 20 | import { PublishDiagnosticsNotification } from "./types"; 21 | import { TextEdit } from "vscode-languageclient/node"; 22 | 23 | /** 24 | * Waits for the editor to stabilize for the specified time 25 | */ 26 | const waitForEditorStabilization = async (timeMs = 1000): Promise => { 27 | return new Promise((resolve) => setTimeout(resolve, timeMs)); 28 | }; 29 | 30 | /** 31 | * Waits for a condition to become true with timeout 32 | */ 33 | const waitForCondition = async (condition: () => boolean, maxAttempts = 10, intervalMs = 1000): Promise => { 34 | for (let i = 0; i < maxAttempts; i++) { 35 | if (condition()) { 36 | return true; 37 | } 38 | await waitForEditorStabilization(intervalMs); 39 | } 40 | return false; 41 | }; 42 | 43 | suite("Extension tests", function () { 44 | // Set timeout for all tests 45 | this.timeout(90000); 46 | 47 | let extension: Extension; 48 | let internals: ExtensionInternal; 49 | 50 | setup(async () => { 51 | try { 52 | await commands.executeCommand("textlint.showOutputChannel"); 53 | const ext = extensions.getExtension("3w36zj6.textlint"); 54 | if (!ext.isActive) { 55 | await ext.activate(); 56 | } 57 | extension = ext; 58 | internals = ext.exports; 59 | 60 | // Verify extension loaded correctly 61 | assert(extension, "Extension not found"); 62 | assert(extension.isActive, "Extension not activated"); 63 | } catch (e) { 64 | console.error("Extension setup failed:", e); 65 | throw e; 66 | } 67 | }); 68 | 69 | suite("Basic behavior", () => { 70 | test("Activate extension", () => { 71 | assert(extension.isActive, "Extension should be active"); 72 | assert(internals.client, "Language client should be initialized"); 73 | assert(internals.statusBar, "Status bar should be initialized"); 74 | }); 75 | 76 | test("Commands registration", async () => { 77 | const allCommands = await commands.getCommands(true); 78 | const textlintCommands = allCommands.filter((cmd) => cmd.startsWith("textlint.")); 79 | const expectedCommands = [ 80 | "textlint.createConfig", 81 | "textlint.applyTextEdits", 82 | "textlint.executeAutofix", 83 | "textlint.showOutputChannel", 84 | ]; 85 | assert.deepStrictEqual(textlintCommands.sort(), expectedCommands.sort(), "Commands should match expected values"); 86 | }); 87 | }); 88 | 89 | suite("Server integration", () => { 90 | // Test file paths 91 | const rootPath = workspace.workspaceFolders[0].uri.fsPath; 92 | const sourceFile = path.join(rootPath, "testtest.txt"); 93 | const testFile = path.join(rootPath, "testtest2.txt"); 94 | 95 | // Event listener disposables 96 | const disposables: Disposable[] = []; 97 | 98 | setup(async () => { 99 | try { 100 | // Restart language server 101 | if (typeof internals.client.stop === "function") { 102 | await internals.client.stop(); 103 | } 104 | if (typeof internals.client.start === "function") { 105 | await internals.client.start(); 106 | } 107 | await internals.client.onReady(); 108 | 109 | // Prepare test file 110 | await fs.copy(sourceFile, testFile); 111 | const exists = await fs.pathExists(testFile); 112 | 113 | if (!exists) { 114 | throw new Error(`Test file could not be created: ${testFile}`); 115 | } 116 | } catch (e) { 117 | console.error("Server restart or file preparation failed:", e); 118 | throw e; 119 | } 120 | }); 121 | 122 | teardown(async () => { 123 | try { 124 | // Delete test file 125 | await fs.unlink(testFile).catch((e) => console.error("Test file deletion failed:", e)); 126 | 127 | // Close editors 128 | await commands.executeCommand("workbench.action.closeAllEditors"); 129 | 130 | // Dispose event listeners 131 | for (const disposable of disposables) { 132 | try { 133 | if (disposable && typeof disposable.dispose === "function") { 134 | disposable.dispose(); 135 | } 136 | } catch (e) { 137 | console.error("Resource disposal failed:", e); 138 | } 139 | } 140 | 141 | // Clear array 142 | disposables.length = 0; 143 | } catch (e) { 144 | console.error("Cleanup failed:", e); 145 | } 146 | }); 147 | 148 | test("Linting", async () => { 149 | const fileUri = Uri.file(testFile); 150 | let diagnosticsReceived = false; 151 | const diagnostics: Diagnostic[] = []; 152 | 153 | // Set up diagnostics notification listener 154 | const disposable = internals.client.onNotification(PublishDiagnosticsNotification.type, (params) => { 155 | const notificationUri = params.uri.toString().toLowerCase(); 156 | const testFileUri = fileUri.toString().toLowerCase(); 157 | 158 | // Process only diagnostics related to test file 159 | if ( 160 | (notificationUri.includes(testFileUri) || testFileUri.includes(notificationUri)) && 161 | params.diagnostics.length > 0 162 | ) { 163 | // Convert and store diagnostics 164 | diagnostics.length = 0; 165 | params.diagnostics.forEach((diag) => { 166 | const vscDiagnostic = internals.client.protocol2CodeConverter.asDiagnostic(diag); 167 | diagnostics.push(vscDiagnostic); 168 | }); 169 | diagnosticsReceived = true; 170 | } 171 | }); 172 | 173 | disposables.push(disposable); 174 | 175 | try { 176 | // Open test file 177 | const doc = await workspace.openTextDocument(testFile); 178 | await window.showTextDocument(doc); 179 | await waitForEditorStabilization(1000); 180 | 181 | // Modify and save file to trigger linting 182 | const edit = new WorkspaceEdit(); 183 | edit.insert(fileUri, new Position(0, 0), " "); 184 | await workspace.applyEdit(edit); 185 | await doc.save(); 186 | 187 | // Show output channel 188 | await commands.executeCommand("textlint.showOutputChannel"); 189 | 190 | // Wait for diagnostics 191 | const received = await waitForCondition(() => diagnosticsReceived); 192 | assert(received, "Should receive diagnostics within 10 seconds"); 193 | 194 | // Expected diagnostics 195 | const expectedDiagnostics = [ 196 | { 197 | code: "common-misspellings", 198 | message: "This is a commonly misspelled word. Correct it to you (common-misspellings)", 199 | range: { 200 | start: { line: 0, character: 1 }, 201 | end: { line: 0, character: 1 }, 202 | }, 203 | severity: DiagnosticSeverity.Error, 204 | source: "textlint", 205 | }, 206 | { 207 | code: "common-misspellings", 208 | message: "This is a commonly misspelled word. Correct it to you (common-misspellings)", 209 | range: { 210 | start: { line: 2, character: 0 }, 211 | end: { line: 2, character: 0 }, 212 | }, 213 | severity: DiagnosticSeverity.Error, 214 | source: "textlint", 215 | }, 216 | { 217 | code: "common-misspellings", 218 | message: "This is a commonly misspelled word. Correct it to you (common-misspellings)", 219 | range: { 220 | start: { line: 4, character: 1 }, 221 | end: { line: 4, character: 1 }, 222 | }, 223 | severity: DiagnosticSeverity.Error, 224 | source: "textlint", 225 | }, 226 | ]; 227 | 228 | // Verify diagnostic count 229 | assert.strictEqual( 230 | diagnostics.length, 231 | expectedDiagnostics.length, 232 | "Number of diagnostics should match expected count" 233 | ); 234 | 235 | // Verify each diagnostic 236 | for (let i = 0; i < expectedDiagnostics.length; i++) { 237 | const actual = diagnostics[i]; 238 | const expected = expectedDiagnostics[i]; 239 | 240 | assert.strictEqual(actual.code, expected.code, `Diagnostic[${i}] code should match expected value`); 241 | 242 | assert.strictEqual(actual.message, expected.message, `Diagnostic[${i}] message should match expected value`); 243 | 244 | assert.strictEqual(actual.source, expected.source, `Diagnostic[${i}] source should match expected value`); 245 | 246 | assert.strictEqual( 247 | actual.severity, 248 | expected.severity, 249 | `Diagnostic[${i}] severity should match expected value` 250 | ); 251 | 252 | // Verify position information 253 | assert.deepStrictEqual( 254 | { 255 | startLine: actual.range.start.line, 256 | startChar: actual.range.start.character, 257 | endLine: actual.range.end.line, 258 | endChar: actual.range.end.character, 259 | }, 260 | { 261 | startLine: expected.range.start.line, 262 | startChar: expected.range.start.character, 263 | endLine: expected.range.end.line, 264 | endChar: expected.range.end.character, 265 | }, 266 | `Diagnostic[${i}] range should match expected values` 267 | ); 268 | } 269 | } catch (e) { 270 | assert.fail(`Lint test failed: ${e}`); 271 | } 272 | }); 273 | 274 | test("Autofix", async () => { 275 | // Fix completion flag and applied edits 276 | let appliedEdits: TextEdit[] = []; 277 | 278 | try { 279 | // Set up fix completion listener 280 | const fixDisposable = internals.onAllFixesComplete((_, edits) => { 281 | appliedEdits = edits || []; 282 | }); 283 | 284 | if (fixDisposable && typeof fixDisposable.dispose === "function") { 285 | disposables.push(fixDisposable); 286 | } 287 | 288 | // Open test file 289 | const doc = await workspace.openTextDocument(testFile); 290 | await window.showTextDocument(doc); 291 | await waitForEditorStabilization(5000); 292 | 293 | // Execute autofix command 294 | await commands.executeCommand("textlint.executeAutofix"); 295 | await waitForEditorStabilization(5000); 296 | 297 | // Expected edits 298 | const expectedEdits = [ 299 | { 300 | range: { 301 | start: { line: 0, character: 0 }, 302 | end: { line: 0, character: 3 }, 303 | }, 304 | newText: "you", 305 | }, 306 | { 307 | range: { 308 | start: { line: 2, character: 0 }, 309 | end: { line: 2, character: 3 }, 310 | }, 311 | newText: "you", 312 | }, 313 | { 314 | range: { 315 | start: { line: 4, character: 1 }, 316 | end: { line: 4, character: 4 }, 317 | }, 318 | newText: "you", 319 | }, 320 | ]; 321 | 322 | // Verify edits were applied 323 | assert.ok(appliedEdits.length > 0, "At least one edit should be applied"); 324 | 325 | // Verify each edit 326 | for (let i = 0; i < expectedEdits.length; i++) { 327 | const actual = appliedEdits[i]; 328 | const expected = expectedEdits[i]; 329 | 330 | assert.strictEqual(actual.newText, expected.newText, `Edit[${i}] newText should match expected value`); 331 | 332 | assert.strictEqual( 333 | actual.range.start.line, 334 | expected.range.start.line, 335 | `Edit[${i}] range.start.line should match expected value` 336 | ); 337 | 338 | assert.strictEqual( 339 | actual.range.start.character, 340 | expected.range.start.character, 341 | `Edit[${i}] range.start.character should match expected value` 342 | ); 343 | 344 | assert.strictEqual( 345 | actual.range.end.line, 346 | expected.range.end.line, 347 | `Edit[${i}] range.end.line should match expected value` 348 | ); 349 | 350 | assert.strictEqual( 351 | actual.range.end.character, 352 | expected.range.end.character, 353 | `Edit[${i}] range.end.character should match expected value` 354 | ); 355 | } 356 | } catch (e) { 357 | assert.fail(`Autofix test failed: ${e}`); 358 | } 359 | }); 360 | }); 361 | }); 362 | 363 | // References: 364 | // https://github.com/Microsoft/vscode-mssql/blob/dev/test/initialization.test.ts 365 | // https://github.com/HookyQR/VSCodeBeautify/blob/master/test/extension.test.js 366 | // https://github.com/Microsoft/vscode-docs/blob/master/docs/extensionAPI/vscode-api-commands.md 367 | // https://github.com/Microsoft/vscode-docs/blob/master/docs/extensionAPI/vscode-api.md 368 | -------------------------------------------------------------------------------- /packages/textlint/test/index.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | import * as Mocha from "mocha"; 3 | import * as glob from "glob"; 4 | 5 | export function run(): Promise { 6 | // Create the mocha test 7 | const mocha = new Mocha({ 8 | ui: "tdd", 9 | color: true, 10 | timeout: 30000, 11 | }); 12 | 13 | const testsRoot = path.resolve(__dirname, "."); 14 | 15 | return new Promise((c, e) => { 16 | glob("**/**.test.js", { cwd: testsRoot }, (err, files) => { 17 | if (err) { 18 | return e(err); 19 | } 20 | 21 | // Add files to the test suite 22 | files.forEach((f) => mocha.addFile(path.resolve(testsRoot, f))); 23 | 24 | try { 25 | // Run the mocha test 26 | mocha.run((failures) => { 27 | if (failures > 0) { 28 | e(new Error(`${failures} tests failed.`)); 29 | } else { 30 | c(); 31 | } 32 | }); 33 | } catch (err) { 34 | console.error(err); 35 | e(err); 36 | } 37 | }); 38 | }); 39 | } 40 | -------------------------------------------------------------------------------- /packages/textlint/test/runTest.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | import { execSync } from "child_process"; 3 | 4 | import { runTests } from "@vscode/test-electron"; 5 | 6 | async function main() { 7 | try { 8 | // The folder containing the Extension Manifest package.json 9 | // Passed to `--extensionDevelopmentPath` 10 | const extensionDevelopmentPath = path.resolve(__dirname, "../../"); 11 | 12 | // The path to the extension test script 13 | // Passed to --extensionTestsPath 14 | const extensionTestsPath = path.resolve(__dirname, "./index"); 15 | 16 | const workspaceFolder = path.resolve(__dirname, "../../../test"); 17 | 18 | try { 19 | console.log("Running npm ci..."); 20 | execSync("npm ci", { 21 | cwd: workspaceFolder, 22 | stdio: "inherit", 23 | }); 24 | console.log("Dependencies installed successfully"); 25 | } catch (error) { 26 | console.error("Failed to install dependencies with npm ci"); 27 | console.error(error); 28 | process.exit(1); 29 | } 30 | 31 | // Download VS Code, unzip it and run the integration test 32 | await runTests({ 33 | extensionDevelopmentPath, 34 | extensionTestsPath, 35 | launchArgs: [workspaceFolder, "--disable-extensions"], 36 | }); 37 | } catch (err) { 38 | console.error("Failed to run tests"); 39 | process.exit(1); 40 | } 41 | } 42 | 43 | main(); 44 | -------------------------------------------------------------------------------- /packages/textlint/test/types.ts: -------------------------------------------------------------------------------- 1 | import { NotificationType } from "vscode-jsonrpc"; 2 | import { Diagnostic } from "vscode-languageserver-types"; 3 | 4 | export namespace PublishDiagnosticsNotification { 5 | export const type = new NotificationType("textDocument/publishDiagnostics"); 6 | } 7 | 8 | export interface PublishDiagnosticsParams { 9 | uri: string; 10 | diagnostics: Diagnostic[]; 11 | } 12 | -------------------------------------------------------------------------------- /packages/textlint/textlint-icon_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/textlint/vscode-textlint/1b37b1b95d081e03681f454c852be34b78d4139e/packages/textlint/textlint-icon_128x128.png -------------------------------------------------------------------------------- /packages/textlint/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "outDir": "out", 6 | "lib": ["es6", "DOM"], 7 | "sourceMap": true 8 | }, 9 | "exclude": ["node_modules", ".vscode-test"] 10 | } 11 | -------------------------------------------------------------------------------- /vscode-textlint.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | }, 6 | { 7 | "path": "packages/textlint" 8 | }, 9 | { 10 | "path": "packages/textlint-server" 11 | }, 12 | { 13 | "path": "packages/test" 14 | }, 15 | { 16 | "path": "packages/test-workspace" 17 | } 18 | ], 19 | "settings": { 20 | "editor.formatOnSave": false, 21 | "editor.codeActionsOnSave": { 22 | "source.fixAll.eslint": "explicit" 23 | }, 24 | "[json]": { 25 | "editor.defaultFormatter": "esbenp.prettier-vscode", 26 | "editor.formatOnSave": true 27 | }, 28 | "[javascript]": { 29 | "editor.defaultFormatter": "esbenp.prettier-vscode", 30 | "editor.formatOnSave": true 31 | }, 32 | "[typescript]": { 33 | "editor.defaultFormatter": "esbenp.prettier-vscode", 34 | "editor.formatOnSave": true 35 | }, 36 | "[typescriptreact]": { 37 | "editor.defaultFormatter": "esbenp.prettier-vscode", 38 | "editor.formatOnSave": true 39 | }, 40 | "typescript.tsdk": "./node_modules/typescript/lib" 41 | }, 42 | "extensions": { 43 | "recommendations": [ 44 | "amodio.tsl-problem-matcher", 45 | "dbaeumer.vscode-eslint", 46 | "editorconfig.editorconfig", 47 | "esbenp.prettier-vscode" 48 | ] 49 | }, 50 | } 51 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const webpack = require("webpack"); 4 | const path = require("path"); 5 | const extensionPackage = require("./packages/textlint/package.json"); 6 | const merge = require("merge-options"); 7 | 8 | /**@type {import('webpack').Configuration}*/ 9 | const config = { 10 | target: "node", 11 | output: { 12 | path: path.resolve(__dirname, "packages/textlint/dist"), 13 | libraryTarget: "commonjs", 14 | }, 15 | stats: { 16 | errorDetails: true, 17 | }, 18 | devtool: "source-map", 19 | externals: [ 20 | { 21 | vscode: "commonjs vscode", 22 | textlint: "commonjs textlint", 23 | }, 24 | ], 25 | resolve: { 26 | extensions: [".ts", ".js"], 27 | modules: [path.resolve(__dirname, "node_modules"), "node_modules"], 28 | }, 29 | module: { 30 | rules: [ 31 | { 32 | test: /\.ts$/, 33 | exclude: /node_modules/, 34 | use: [ 35 | { 36 | loader: "ts-loader", 37 | }, 38 | ], 39 | }, 40 | ], 41 | }, 42 | }; 43 | 44 | /**@type {import('webpack').Configuration}*/ 45 | const client = merge(config, { 46 | entry: "./packages/textlint/src/extension.ts", 47 | output: { 48 | filename: "extension.js", 49 | }, 50 | plugins: [ 51 | new webpack.EnvironmentPlugin({ 52 | EXTENSION_NAME: `${extensionPackage.publisher}.${extensionPackage.name}`, 53 | EXTENSION_VERSION: extensionPackage.version, 54 | }), 55 | ], 56 | }); 57 | 58 | /**@type {import('webpack').Configuration}*/ 59 | const server = merge(config, { 60 | entry: "./packages/textlint-server/src/server.ts", 61 | output: { 62 | filename: "server.js", 63 | }, 64 | }); 65 | 66 | module.exports = [client, server]; 67 | --------------------------------------------------------------------------------