├── .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 | 
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 |
2 | TODO: Expect error.
3 |
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 | 
10 |
11 | 
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 |
--------------------------------------------------------------------------------