mirror of
https://github.com/cline/cline.git
synced 2026-09-02 07:42:19 +08:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4f562d9f80 | |||
| 8ffed7a996 | |||
| 65b460d255 | |||
| 9de86f8b48 |
@@ -1,35 +0,0 @@
|
||||
{
|
||||
"root": true,
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 6,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": ["@typescript-eslint", "eslint-rules"],
|
||||
"rules": {
|
||||
"@typescript-eslint/naming-convention": [
|
||||
"warn",
|
||||
{
|
||||
"selector": "import",
|
||||
"format": ["camelCase", "PascalCase"]
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/semi": "off",
|
||||
"curly": "warn",
|
||||
"eqeqeq": "warn",
|
||||
"no-throw-literal": "warn",
|
||||
"semi": "off",
|
||||
"react-hooks/exhaustive-deps": "off",
|
||||
"eslint-rules/no-protobuf-object-literals": "error",
|
||||
"eslint-rules/no-grpc-client-object-literals": "error",
|
||||
"eslint-rules/no-direct-vscode-api": "warn",
|
||||
"no-restricted-syntax": [
|
||||
"error",
|
||||
{
|
||||
"selector": "VariableDeclarator[id.type=\"ObjectPattern\"][init.object.name=\"process\"][init.property.name=\"env\"]",
|
||||
"message": "Use process.env.VARIABLE_NAME directly instead of destructuring"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ignorePatterns": ["out", "dist", "**/*.d.ts"]
|
||||
}
|
||||
+184
-190
@@ -1,230 +1,224 @@
|
||||
name: Tests
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
workflow_call:
|
||||
|
||||
# Set default permissions for all jobs
|
||||
permissions:
|
||||
contents: read # Needed to check out code
|
||||
checks: write # Needed to report test results
|
||||
pull-requests: write # Needed to add comments/annotations to PRs
|
||||
contents: read # Needed to check out code
|
||||
checks: write # Needed to report test results
|
||||
pull-requests: write # Needed to add comments/annotations to PRs
|
||||
|
||||
jobs:
|
||||
test:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
name: ${{ matrix.os == 'ubuntu-latest' && 'test' || format('test ({0})', matrix.os) }}
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
test:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
name: ${{ matrix.os == 'ubuntu-latest' && 'test' || format('test ({0})', matrix.os) }}
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js environment
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
- name: Setup Node.js environment
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
|
||||
# Setup Python for coverage script
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.10"
|
||||
# Setup Python for coverage script
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.10"
|
||||
|
||||
- name: Install Python dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install requests
|
||||
- name: Install Python dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install requests
|
||||
|
||||
# Cache root dependencies - only reuse if package-lock.json exactly matches
|
||||
- name: Cache root dependencies
|
||||
uses: actions/cache@v4
|
||||
id: root-cache
|
||||
with:
|
||||
path: node_modules
|
||||
key: ${{ runner.os }}-npm-${{ hashFiles('package-lock.json') }}
|
||||
# Cache root dependencies - only reuse if package-lock.json exactly matches
|
||||
- name: Cache root dependencies
|
||||
uses: actions/cache@v4
|
||||
id: root-cache
|
||||
with:
|
||||
path: node_modules
|
||||
key: ${{ runner.os }}-npm-${{ hashFiles('package-lock.json') }}
|
||||
|
||||
# Cache webview-ui dependencies - only reuse if package-lock.json exactly matches
|
||||
- name: Cache webview-ui dependencies
|
||||
uses: actions/cache@v4
|
||||
id: webview-cache
|
||||
with:
|
||||
path: webview-ui/node_modules
|
||||
key: ${{ runner.os }}-npm-webview-${{ hashFiles('webview-ui/package-lock.json') }}
|
||||
# Cache webview-ui dependencies - only reuse if package-lock.json exactly matches
|
||||
- name: Cache webview-ui dependencies
|
||||
uses: actions/cache@v4
|
||||
id: webview-cache
|
||||
with:
|
||||
path: webview-ui/node_modules
|
||||
key: ${{ runner.os }}-npm-webview-${{ hashFiles('webview-ui/package-lock.json') }}
|
||||
|
||||
- name: Install root dependencies
|
||||
if: steps.root-cache.outputs.cache-hit != 'true'
|
||||
run: npm ci
|
||||
- name: Install root dependencies
|
||||
if: steps.root-cache.outputs.cache-hit != 'true'
|
||||
run: npm ci
|
||||
|
||||
- name: Install webview-ui dependencies
|
||||
if: steps.webview-cache.outputs.cache-hit != 'true'
|
||||
run: cd webview-ui && npm ci
|
||||
- name: Install webview-ui dependencies
|
||||
if: steps.webview-cache.outputs.cache-hit != 'true'
|
||||
run: cd webview-ui && npm ci
|
||||
|
||||
- name: Install xvfb on Linux
|
||||
if: runner.os == 'Linux'
|
||||
run: sudo apt-get update && sudo apt-get install -y xvfb
|
||||
- name: Install xvfb on Linux
|
||||
if: runner.os == 'Linux'
|
||||
run: sudo apt-get update && sudo apt-get install -y xvfb
|
||||
|
||||
- name: Install local modules on windows
|
||||
if: runner.os == 'Windows' && steps.root-cache.outputs.cache-hit == 'true'
|
||||
run: |
|
||||
npm install eslint-plugin-eslint-rules
|
||||
cd webview-ui/ && npm install eslint-plugin-eslint-rules
|
||||
- name: Set up NPM on Windows
|
||||
if: runner.os == 'Windows'
|
||||
run: |
|
||||
npm config set script-shell "C:\\Program Files\\Git\\bin\\bash.exe"
|
||||
|
||||
- name: Set up NPM on Windows
|
||||
if: runner.os == 'Windows'
|
||||
run: |
|
||||
npm config set script-shell "C:\\Program Files\\Git\\bin\\bash.exe"
|
||||
- name: Type Check
|
||||
run: npm run check-types
|
||||
|
||||
- name: Type Check
|
||||
run: npm run check-types
|
||||
- name: ESLint Check
|
||||
run: npm run lint
|
||||
|
||||
- name: ESLint Check
|
||||
run: npm run lint
|
||||
- name: Prettier / Format Check
|
||||
run: npm run format
|
||||
|
||||
- name: Prettier / Format Check
|
||||
run: npm run format
|
||||
# Build the extension before running tests
|
||||
- name: Build Tests and Extension
|
||||
run: npm run pretest
|
||||
|
||||
# Build the extension before running tests
|
||||
- name: Build Tests and Extension
|
||||
run: npm run pretest
|
||||
# Unit Tests disabled due to module system conflicts between backend and webview-ui
|
||||
# - name: Unit Tests
|
||||
# run: npm run test:unit
|
||||
|
||||
# Unit Tests disabled due to module system conflicts between backend and webview-ui
|
||||
# - name: Unit Tests
|
||||
# run: npm run test:unit
|
||||
# Run extension tests with coverage
|
||||
- name: Extension Tests with Coverage
|
||||
id: extension_coverage
|
||||
continue-on-error: true
|
||||
run: |
|
||||
node ./scripts/test-ci.js > extension_coverage.txt 2>&1
|
||||
# Default the encoding to UTF-8 - It's not the default on Windows
|
||||
PYTHONUTF8=1 PYTHONPATH=.github/scripts python -m coverage_check extract-coverage extension_coverage.txt --type=extension --github-output --verbose
|
||||
|
||||
# Run extension tests with coverage
|
||||
- name: Extension Tests with Coverage
|
||||
id: extension_coverage
|
||||
continue-on-error: true
|
||||
run: |
|
||||
node ./scripts/test-ci.js > extension_coverage.txt 2>&1
|
||||
# Default the encoding to UTF-8 - It's not the default on Windows
|
||||
PYTHONUTF8=1 PYTHONPATH=.github/scripts python -m coverage_check extract-coverage extension_coverage.txt --type=extension --github-output --verbose
|
||||
# Run webview tests with coverage
|
||||
- name: Webview Tests with Coverage
|
||||
id: webview_coverage
|
||||
continue-on-error: true
|
||||
run: |
|
||||
cd webview-ui
|
||||
# Ensure coverage dependency is installed
|
||||
npm install --no-save @vitest/coverage-v8
|
||||
npm run test:coverage > webview_coverage.txt 2>&1
|
||||
cd ..
|
||||
# Default the encoding to UTF-8 - It's not the default on Windows
|
||||
PYTHONUTF8=1 PYTHONPATH=.github/scripts python -m coverage_check extract-coverage webview-ui/webview_coverage.txt --type=webview --github-output --verbose
|
||||
|
||||
# Run webview tests with coverage
|
||||
- name: Webview Tests with Coverage
|
||||
id: webview_coverage
|
||||
continue-on-error: true
|
||||
run: |
|
||||
cd webview-ui
|
||||
# Ensure coverage dependency is installed
|
||||
npm install --no-save @vitest/coverage-v8
|
||||
npm run test:coverage > webview_coverage.txt 2>&1
|
||||
cd ..
|
||||
# Default the encoding to UTF-8 - It's not the default on Windows
|
||||
PYTHONUTF8=1 PYTHONPATH=.github/scripts python -m coverage_check extract-coverage webview-ui/webview_coverage.txt --type=webview --github-output --verbose
|
||||
# Save coverage reports as artifacts (workflow-scoped)
|
||||
- name: Save Coverage Reports
|
||||
uses: actions/upload-artifact@v4
|
||||
# Only upload artifacts on Linux - We only need coverage from one OS
|
||||
if: runner.os == 'Linux'
|
||||
with:
|
||||
name: pr-coverage-reports
|
||||
path: |
|
||||
extension_coverage.txt
|
||||
webview-ui/webview_coverage.txt
|
||||
retention-days: 1 # Artifacts are automatically deleted after 1 day (minimum retention) https://docs.github.com/en/actions/concepts/billing-and-usage#artifact-and-log-retention-policy
|
||||
|
||||
# Save coverage reports as artifacts (workflow-scoped)
|
||||
- name: Save Coverage Reports
|
||||
uses: actions/upload-artifact@v4
|
||||
# Only upload artifacts on Linux - We only need coverage from one OS
|
||||
if: runner.os == 'Linux'
|
||||
with:
|
||||
name: pr-coverage-reports
|
||||
path: |
|
||||
extension_coverage.txt
|
||||
webview-ui/webview_coverage.txt
|
||||
retention-period: workflow # Artifacts are automatically deleted when the workflow completes
|
||||
# Set the check as failed if any of the tests failed
|
||||
- name: Print test results and check for failures
|
||||
run: |
|
||||
echo "Extension Tests Result: ${{ steps.extension_coverage.outcome }}"
|
||||
cat extension_coverage.txt
|
||||
|
||||
# Set the check as failed if any of the tests failed
|
||||
- name: Print test results and check for failures
|
||||
run: |
|
||||
echo "Extension Tests Result: ${{ steps.extension_coverage.outcome }}"
|
||||
cat extension_coverage.txt
|
||||
echo "Webview Tests Result: ${{ steps.webview_coverage.outcome }}"
|
||||
cat webview-ui/webview_coverage.txt
|
||||
|
||||
echo "Webview Tests Result: ${{ steps.webview_coverage.outcome }}"
|
||||
cat webview-ui/webview_coverage.txt
|
||||
# Check if any of the test steps failed
|
||||
# https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/accessing-contextual-information-about-workflow-runs#steps-context
|
||||
if [ "${{ steps.extension_coverage.outcome }}" != "success" ] || [ "${{ steps.webview_coverage.outcome }}" != "success" ]; then
|
||||
echo "Tests failed."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if any of the test steps failed
|
||||
# https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/accessing-contextual-information-about-workflow-runs#steps-context
|
||||
if [ "${{ steps.extension_coverage.outcome }}" != "success" ] || [ "${{ steps.webview_coverage.outcome }}" != "success" ]; then
|
||||
echo "Tests failed."
|
||||
exit 1
|
||||
fi
|
||||
coverage:
|
||||
needs: test
|
||||
runs-on: ubuntu-latest
|
||||
# Only run on PRs to main branch
|
||||
if: github.event_name == 'pull_request' && github.base_ref == 'main'
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0 # Fetch all history for accurate comparison
|
||||
|
||||
coverage:
|
||||
needs: test
|
||||
runs-on: ubuntu-latest
|
||||
# Only run on PRs to main branch
|
||||
if: github.event_name == 'pull_request' && github.base_ref == 'main'
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0 # Fetch all history for accurate comparison
|
||||
# Setup Python for coverage script
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.10"
|
||||
|
||||
# Setup Python for coverage script
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.10"
|
||||
- name: Install Python dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install requests
|
||||
|
||||
- name: Install Python dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install requests
|
||||
- name: Setup Node.js environment
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
|
||||
- name: Setup Node.js environment
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
# Cache root dependencies - only reuse if package-lock.json exactly matches
|
||||
- name: Cache root dependencies
|
||||
uses: actions/cache@v4
|
||||
id: root-cache
|
||||
with:
|
||||
path: node_modules
|
||||
key: ${{ runner.os }}-npm-${{ hashFiles('package-lock.json') }}
|
||||
|
||||
# Cache root dependencies - only reuse if package-lock.json exactly matches
|
||||
- name: Cache root dependencies
|
||||
uses: actions/cache@v4
|
||||
id: root-cache
|
||||
with:
|
||||
path: node_modules
|
||||
key: ${{ runner.os }}-npm-${{ hashFiles('package-lock.json') }}
|
||||
# Cache webview-ui dependencies - only reuse if package-lock.json exactly matches
|
||||
- name: Cache webview-ui dependencies
|
||||
uses: actions/cache@v4
|
||||
id: webview-cache
|
||||
with:
|
||||
path: webview-ui/node_modules
|
||||
key: ${{ runner.os }}-npm-webview-${{ hashFiles('webview-ui/package-lock.json') }}
|
||||
|
||||
# Cache webview-ui dependencies - only reuse if package-lock.json exactly matches
|
||||
- name: Cache webview-ui dependencies
|
||||
uses: actions/cache@v4
|
||||
id: webview-cache
|
||||
with:
|
||||
path: webview-ui/node_modules
|
||||
key: ${{ runner.os }}-npm-webview-${{ hashFiles('webview-ui/package-lock.json') }}
|
||||
- name: Install root dependencies
|
||||
if: steps.root-cache.outputs.cache-hit != 'true'
|
||||
run: npm ci
|
||||
|
||||
- name: Install root dependencies
|
||||
if: steps.root-cache.outputs.cache-hit != 'true'
|
||||
run: npm ci
|
||||
- name: Install webview-ui dependencies
|
||||
if: steps.webview-cache.outputs.cache-hit != 'true'
|
||||
run: cd webview-ui && npm ci
|
||||
|
||||
- name: Install webview-ui dependencies
|
||||
if: steps.webview-cache.outputs.cache-hit != 'true'
|
||||
run: cd webview-ui && npm ci
|
||||
# Build the extension before running tests
|
||||
- name: Build Extension
|
||||
run: npm run compile
|
||||
|
||||
# Build the extension before running tests
|
||||
- name: Build Extension
|
||||
run: npm run compile
|
||||
# Download coverage artifacts from test job
|
||||
- name: Download Coverage Reports
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: pr-coverage-reports
|
||||
path: . # Download to root directory to match expected paths
|
||||
|
||||
# Download coverage artifacts from test job
|
||||
- name: Download Coverage Reports
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: pr-coverage-reports
|
||||
path: . # Download to root directory to match expected paths
|
||||
# Process coverage workflow
|
||||
- name: Process coverage workflow
|
||||
id: coverage
|
||||
run: |
|
||||
# Extract PR number from GITHUB_REF
|
||||
PR_NUMBER=$(echo "$GITHUB_REF" | sed -e 's/refs\/pull\///' -e 's/\/merge//')
|
||||
|
||||
# Process coverage workflow
|
||||
- name: Process coverage workflow
|
||||
id: coverage
|
||||
run: |
|
||||
# Extract PR number from GITHUB_REF
|
||||
PR_NUMBER=$(echo "$GITHUB_REF" | sed -e 's/refs\/pull\///' -e 's/\/merge//')
|
||||
|
||||
# Run the coverage workflow from root directory
|
||||
PYTHONPATH=.github/scripts python -m coverage_check process-workflow \
|
||||
--base-branch ${{ github.base_ref }} \
|
||||
--pr-number $PR_NUMBER \
|
||||
--repo $GITHUB_REPOSITORY \
|
||||
--token ${{ secrets.GITHUB_TOKEN }} \
|
||||
--verbose
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
# Run the coverage workflow from root directory
|
||||
PYTHONPATH=.github/scripts python -m coverage_check process-workflow \
|
||||
--base-branch ${{ github.base_ref }} \
|
||||
--pr-number $PR_NUMBER \
|
||||
--repo $GITHUB_REPOSITORY \
|
||||
--token ${{ secrets.GITHUB_TOKEN }} \
|
||||
--verbose
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
+1
-17
@@ -1,17 +1 @@
|
||||
echo "Running pre-commit checks..."
|
||||
|
||||
# Run ESLint
|
||||
echo "Running ESLint..."
|
||||
npm run lint || {
|
||||
echo "❌ ESLint check failed. Please fix the errors and try committing again."
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Run Prettier
|
||||
echo "Running Prettier..."
|
||||
npx lint-staged --verbose || {
|
||||
echo "❌ Prettier failed. Please fix the errors and try committing again."
|
||||
exit 1
|
||||
}
|
||||
|
||||
echo "✅ All checks passed!"
|
||||
lint-staged
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"extension": ["ts"],
|
||||
"spec": ["src/**/__tests__/*.ts", "eslint-rules/__tests__/**/*.test.ts"],
|
||||
"spec": ["src/**/__tests__/*.ts"],
|
||||
"require": ["ts-node/register", "source-map-support/register", "./src/test/requires.ts"],
|
||||
"recursive": true
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
dist/
|
||||
node_modules
|
||||
webview-ui/build/
|
||||
*.md
|
||||
package-lock.json
|
||||
src/core/prompts/system.ts
|
||||
src/core/prompts/model_prompts/claude4.ts
|
||||
evals/
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"tabWidth": 4,
|
||||
"useTabs": true,
|
||||
"printWidth": 130,
|
||||
"semi": false,
|
||||
"bracketSameLine": true,
|
||||
"endOfLine": "lf"
|
||||
}
|
||||
Vendored
+2
-1
@@ -5,6 +5,7 @@
|
||||
"dbaeumer.vscode-eslint",
|
||||
"connor4312.esbuild-problem-matchers",
|
||||
"ms-vscode.extension-test-runner",
|
||||
"bradlc.vscode-tailwindcss"
|
||||
"bradlc.vscode-tailwindcss",
|
||||
"biomejs.biome"
|
||||
]
|
||||
}
|
||||
|
||||
Vendored
+28
-2
@@ -6,8 +6,34 @@
|
||||
},
|
||||
"search.exclude": {
|
||||
"out": true, // set this to false to include "out" folder in search results
|
||||
"dist": true // set this to false to include "dist" folder in search results
|
||||
"dist": true, // set this to false to include "dist" folder in search results,
|
||||
"node_modules": true,
|
||||
"dist-standalone": true
|
||||
},
|
||||
// Turn off tsc task auto detection since we have the necessary tasks as npm scripts
|
||||
"typescript.tsc.autoDetect": "off"
|
||||
"typescript.tsc.autoDetect": "off",
|
||||
"editor.formatOnSave": true,
|
||||
|
||||
// Lint and format using Biome
|
||||
"biome.enabled": true,
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
},
|
||||
"[typescriptreact]": {
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
},
|
||||
"[jsonc]": {
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
},
|
||||
"[json]": {
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
},
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.organizeImports": "never",
|
||||
"source.organizeImports.biome": "explicit",
|
||||
"source.removeUnusedImports": "always",
|
||||
"source.fixAll.biome": "always"
|
||||
},
|
||||
"editor.insertSpaces": true,
|
||||
"typescript.preferences.quoteStyle": "single"
|
||||
}
|
||||
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
{
|
||||
"vcs": {
|
||||
"enabled": true,
|
||||
"clientKind": "git",
|
||||
"useIgnoreFile": true,
|
||||
"defaultBranch": "main"
|
||||
},
|
||||
"assist": {
|
||||
"actions": {
|
||||
"source": {
|
||||
"organizeImports": "off"
|
||||
}
|
||||
}
|
||||
},
|
||||
"linter": {
|
||||
"enabled": true,
|
||||
"domains": {
|
||||
"react": "recommended"
|
||||
},
|
||||
"rules": {
|
||||
"recommended": true,
|
||||
"correctness": {
|
||||
"useExhaustiveDependencies": "off",
|
||||
"noUndeclaredVariables": "off",
|
||||
"noUnusedVariables": "off"
|
||||
},
|
||||
"a11y": "off",
|
||||
"style": {
|
||||
"useNodejsImportProtocol": "info",
|
||||
"useImportType": "off",
|
||||
"useBlockStatements": "warn",
|
||||
"useNamingConvention": {
|
||||
"level": "info",
|
||||
"options": {
|
||||
"strictCase": true,
|
||||
"requireAscii": false,
|
||||
"conventions": [
|
||||
{
|
||||
"selector": { "kind": "importNamespace" },
|
||||
"formats": ["camelCase", "PascalCase"]
|
||||
},
|
||||
{
|
||||
"selector": { "kind": "importAlias" },
|
||||
"formats": ["camelCase", "PascalCase"]
|
||||
},
|
||||
{
|
||||
"selector": { "kind": "function" },
|
||||
"formats": ["camelCase", "PascalCase"]
|
||||
},
|
||||
{
|
||||
"selector": { "kind": "variable" },
|
||||
"formats": ["camelCase", "PascalCase", "CONSTANT_CASE"]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"useThrowOnlyError": "info",
|
||||
"useConsistentArrayType": "off"
|
||||
},
|
||||
"suspicious": {
|
||||
"noDoubleEquals": "warn",
|
||||
"noImplicitAnyLet": "info",
|
||||
"noMisleadingCharacterClass": "error",
|
||||
"noUnsafeDeclarationMerging": "error"
|
||||
},
|
||||
"complexity": {
|
||||
"noBannedTypes": "error",
|
||||
"noUselessConstructor": "warn",
|
||||
"useOptionalChain": "warn"
|
||||
},
|
||||
"security": {
|
||||
"noDangerouslySetInnerHtml": "error"
|
||||
}
|
||||
}
|
||||
},
|
||||
"formatter": {
|
||||
"enabled": true,
|
||||
"indentStyle": "tab",
|
||||
"indentWidth": 4,
|
||||
"lineWidth": 130,
|
||||
"lineEnding": "lf",
|
||||
"formatWithErrors": false
|
||||
},
|
||||
"javascript": {
|
||||
"linter": {
|
||||
"enabled": true
|
||||
},
|
||||
"formatter": {
|
||||
"semicolons": "asNeeded",
|
||||
"arrowParentheses": "always",
|
||||
"bracketSameLine": true,
|
||||
"bracketSpacing": true,
|
||||
"jsxQuoteStyle": "double",
|
||||
"quoteProperties": "asNeeded",
|
||||
"trailingCommas": "es5"
|
||||
}
|
||||
},
|
||||
"json": {
|
||||
"linter": {
|
||||
"enabled": true
|
||||
},
|
||||
"formatter": {
|
||||
"indentStyle": "tab",
|
||||
"indentWidth": 4,
|
||||
"lineWidth": 130,
|
||||
"bracketSpacing": true,
|
||||
"trailingCommas": "none"
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"includes": [
|
||||
"**",
|
||||
"!**/node_modules/**",
|
||||
"!**/out/**",
|
||||
"!**/dist/**",
|
||||
"!**/build/**",
|
||||
"!**/dist-*/",
|
||||
"!**/.vscode*/**",
|
||||
"!**/*.d.ts",
|
||||
"!**/*-lock.json",
|
||||
"!**/generated/**",
|
||||
"src/host/vscode.d.ts",
|
||||
"!*.md*",
|
||||
"!package-lock.json",
|
||||
"!src/core/prompts/system.ts",
|
||||
"!src/core/prompts/model_prompts/claude4.ts",
|
||||
"!evals/",
|
||||
"!**/playwright/**",
|
||||
"!**/test-rests/**"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
const { RuleTester: DirectApiRuleTester } = require("eslint")
|
||||
const noDirectVscodeApiRule = require("../no-direct-vscode-api")
|
||||
|
||||
const directApiRuleTester = new DirectApiRuleTester({
|
||||
parser: require.resolve("@typescript-eslint/parser"),
|
||||
parserOptions: {
|
||||
ecmaVersion: 2020,
|
||||
sourceType: "module",
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
directApiRuleTester.run("no-direct-vscode-api", noDirectVscodeApiRule, {
|
||||
valid: [
|
||||
// Should allow vscode.postMessage in grpc-client-base.ts
|
||||
{
|
||||
code: `vscode.postMessage({ type: "grpc_request", data: {} })`,
|
||||
filename: "grpc-client-base.ts",
|
||||
},
|
||||
{
|
||||
code: `vscode.postMessage({ type: "grpc_request_cancel" })`,
|
||||
filename: "/path/to/grpc-client-base.ts",
|
||||
},
|
||||
// Should allow in exception directories
|
||||
{
|
||||
code: `vscode.workspace.workspaceFolders`,
|
||||
filename: "/src/hosts/vscode/host-bridge.ts",
|
||||
},
|
||||
{
|
||||
code: `vscode.workspace.fs.stat(uri)`,
|
||||
filename: "/standalone/runtime-files/helpers.ts",
|
||||
},
|
||||
// Should allow other vscode API calls
|
||||
{
|
||||
code: `vscode.window.showInformationMessage("Hello")`,
|
||||
filename: "test.ts",
|
||||
},
|
||||
// Should allow postMessage calls on other objects
|
||||
{
|
||||
code: `window.postMessage({ type: "test" }, "*")`,
|
||||
filename: "test.ts",
|
||||
},
|
||||
// Should allow variables named vscode but not calling postMessage
|
||||
{
|
||||
code: `const vscode = { other: "method" }; vscode.other()`,
|
||||
filename: "test.ts",
|
||||
},
|
||||
],
|
||||
invalid: [
|
||||
// Should disallow vscode.postMessage in regular files
|
||||
{
|
||||
code: `vscode.postMessage({ type: "test", data: {} })`,
|
||||
filename: "test.ts",
|
||||
errors: [
|
||||
{
|
||||
messageId: "useGrpcClient",
|
||||
},
|
||||
],
|
||||
},
|
||||
// Should disallow vscode.postMessage in components
|
||||
{
|
||||
code: `vscode.postMessage({ type: "apiConfiguration", apiConfiguration })`,
|
||||
filename: "ApiOptions.tsx",
|
||||
errors: [
|
||||
{
|
||||
messageId: "useGrpcClient",
|
||||
},
|
||||
],
|
||||
},
|
||||
// Should disallow vscode.postMessage in test files
|
||||
{
|
||||
code: `vscode.postMessage({ type: "newTask", text: message.text })`,
|
||||
filename: "test.test.ts",
|
||||
errors: [
|
||||
{
|
||||
messageId: "useGrpcClient",
|
||||
},
|
||||
],
|
||||
},
|
||||
// Should disallow property access for disallowed APIs
|
||||
{
|
||||
code: `const folders = vscode.workspace.workspaceFolders;`,
|
||||
filename: "workspace.ts",
|
||||
errors: [
|
||||
{
|
||||
messageId: "useHostBridge",
|
||||
},
|
||||
],
|
||||
},
|
||||
// Should disallow method calls for disallowed APIs
|
||||
{
|
||||
code: `const relativePath = vscode.workspace.asRelativePath(filePath);`,
|
||||
filename: "path-utils.ts",
|
||||
errors: [
|
||||
{
|
||||
messageId: "usePathUtils",
|
||||
},
|
||||
],
|
||||
},
|
||||
// Should disallow nested property access
|
||||
{
|
||||
code: `const stats = await vscode.workspace.fs.stat(uri);`,
|
||||
filename: "file-utils.ts",
|
||||
errors: [
|
||||
{
|
||||
messageId: "useFsUtils",
|
||||
},
|
||||
],
|
||||
},
|
||||
// Should disallow getting a workspace folder
|
||||
{
|
||||
code: `const folder = vscode.workspace.getWorkspaceFolder(uri);`,
|
||||
filename: "path-helper.ts",
|
||||
errors: [
|
||||
{
|
||||
messageId: "usePathUtils",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
@@ -1,174 +0,0 @@
|
||||
const { RuleTester: GrpcRuleTester } = require("eslint")
|
||||
const grpcRule = require("../no-grpc-client-object-literals")
|
||||
|
||||
const grpcRuleTester = new GrpcRuleTester({
|
||||
parser: require.resolve("@typescript-eslint/parser"),
|
||||
parserOptions: {
|
||||
ecmaVersion: 2020,
|
||||
sourceType: "module",
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
grpcRuleTester.run("no-grpc-client-object-literals", grpcRule, {
|
||||
valid: [
|
||||
// Valid case: Using .create() method with gRPC client
|
||||
{
|
||||
code: `
|
||||
import { TogglePlanActModeRequest } from '@shared/proto/state';
|
||||
import { StateServiceClient } from '../services/grpc-client';
|
||||
|
||||
StateServiceClient.togglePlanActMode(
|
||||
TogglePlanActModeRequest.create({
|
||||
chatSettings: {
|
||||
mode: PlanActMode.PLAN,
|
||||
preferredLanguage: 'en',
|
||||
},
|
||||
})
|
||||
);
|
||||
`,
|
||||
},
|
||||
// Valid case: Using .fromPartial() method with gRPC client
|
||||
{
|
||||
code: `
|
||||
import { TogglePlanActModeRequest, ChatSettings } from '@shared/proto/state';
|
||||
import { StateServiceClient } from '../services/grpc-client';
|
||||
|
||||
const chatSettings = ChatSettings.fromPartial({
|
||||
mode: PlanActMode.PLAN,
|
||||
preferredLanguage: 'en',
|
||||
});
|
||||
|
||||
StateServiceClient.togglePlanActMode(
|
||||
TogglePlanActModeRequest.create({
|
||||
chatSettings: chatSettings,
|
||||
})
|
||||
);
|
||||
`,
|
||||
},
|
||||
// Valid case: Regular function call with object literal (not a gRPC client)
|
||||
{
|
||||
code: `
|
||||
function processData(data) {
|
||||
console.log(data);
|
||||
}
|
||||
|
||||
processData({
|
||||
id: 123,
|
||||
name: 'test',
|
||||
});
|
||||
`,
|
||||
},
|
||||
// Valid case: Using proper nested protobuf objects
|
||||
{
|
||||
code: `
|
||||
import { TogglePlanActModeRequest, ChatSettings } from '@shared/proto/state';
|
||||
import { StateServiceClient } from '../services/grpc-client';
|
||||
|
||||
// Using proper nested protobuf objects
|
||||
const chatSettings = ChatSettings.create({
|
||||
mode: 0,
|
||||
preferredLanguage: 'en',
|
||||
});
|
||||
|
||||
const request = TogglePlanActModeRequest.create({
|
||||
chatSettings: chatSettings,
|
||||
});
|
||||
|
||||
StateServiceClient.togglePlanActMode(request);
|
||||
`,
|
||||
},
|
||||
// Valid case: Object literal in second parameter (should not be checked)
|
||||
{
|
||||
code: `
|
||||
import { StateSubscribeRequest } from '@shared/proto/state';
|
||||
import { StateServiceClient } from '../services/grpc-client';
|
||||
|
||||
const request = StateSubscribeRequest.create({
|
||||
topics: ['apiConfig', 'tasks']
|
||||
});
|
||||
|
||||
// Second parameter is an object literal but should not trigger the rule
|
||||
StateServiceClient.subscribe(request, {
|
||||
metadata: {
|
||||
userId: 123,
|
||||
sessionId: "abc-123"
|
||||
}
|
||||
});
|
||||
`,
|
||||
},
|
||||
],
|
||||
invalid: [
|
||||
// Invalid case: Using object literal directly with gRPC client
|
||||
{
|
||||
code: `
|
||||
import { StateServiceClient } from '../services/grpc-client';
|
||||
|
||||
StateServiceClient.togglePlanActMode({
|
||||
chatSettings: {
|
||||
mode: 0,
|
||||
preferredLanguage: 'en',
|
||||
},
|
||||
});
|
||||
`,
|
||||
errors: [{ messageId: "useProtobufMethod" }],
|
||||
},
|
||||
// Invalid case: Using object literal with nested properties
|
||||
{
|
||||
code: `
|
||||
import { ChatSettings } from '@shared/proto/state';
|
||||
import { StateServiceClient } from '../services/grpc-client';
|
||||
|
||||
const chatSettings = ChatSettings.create({
|
||||
mode: 0,
|
||||
preferredLanguage: 'en',
|
||||
});
|
||||
|
||||
StateServiceClient.togglePlanActMode({
|
||||
chatSettings: {
|
||||
mode: 1,
|
||||
preferredLanguage: 'fr',
|
||||
},
|
||||
});
|
||||
`,
|
||||
errors: [{ messageId: "useProtobufMethod" }],
|
||||
},
|
||||
// Invalid case: Nested object literal in protobuf create method
|
||||
{
|
||||
code: `
|
||||
import { TogglePlanActModeRequest, ChatSettings } from '@shared/proto/state';
|
||||
import { StateServiceClient } from '../services/grpc-client';
|
||||
|
||||
// Using nested object literal instead of ChatSettings.create()
|
||||
const request = TogglePlanActModeRequest.create({
|
||||
chatSettings: {
|
||||
mode: 0,
|
||||
preferredLanguage: 'en',
|
||||
},
|
||||
});
|
||||
|
||||
StateServiceClient.togglePlanActMode(request);
|
||||
`,
|
||||
errors: [{ messageId: "useProtobufMethod" }],
|
||||
},
|
||||
// Invalid case: Object literal as first parameter to subscribe method
|
||||
{
|
||||
code: `
|
||||
import { StateServiceClient } from '../services/grpc-client';
|
||||
|
||||
// First parameter is an object literal, which should trigger the rule
|
||||
StateServiceClient.subscribe({
|
||||
topics: ['apiConfig', 'tasks']
|
||||
}, {
|
||||
metadata: {
|
||||
userId: 123,
|
||||
sessionId: "abc-123"
|
||||
}
|
||||
});
|
||||
`,
|
||||
errors: [{ messageId: "useProtobufMethod" }],
|
||||
},
|
||||
],
|
||||
})
|
||||
@@ -1,214 +0,0 @@
|
||||
const { RuleTester } = require("eslint")
|
||||
const rule = require("../no-protobuf-object-literals")
|
||||
|
||||
const ruleTester = new RuleTester({
|
||||
parser: require.resolve("@typescript-eslint/parser"),
|
||||
parserOptions: {
|
||||
ecmaVersion: 2020,
|
||||
sourceType: "module",
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
ruleTester.run("no-protobuf-object-literals", rule, {
|
||||
valid: [
|
||||
// Valid case: Using .create() method
|
||||
{
|
||||
code: `
|
||||
import { State } from '@shared/proto/state';
|
||||
|
||||
const state = State.create({
|
||||
stateJson: '{"apiConfig":{"provider":"anthropic","model":"claude-3-haiku"}}'
|
||||
});
|
||||
`,
|
||||
},
|
||||
// Valid case: Using .fromPartial() method
|
||||
{
|
||||
code: `
|
||||
import { ChatSettings } from '@shared/proto/state';
|
||||
|
||||
const settings = ChatSettings.fromPartial({
|
||||
mode: 0,
|
||||
preferredLanguage: 'en',
|
||||
openAiReasoningEffort: 'thorough'
|
||||
});
|
||||
`,
|
||||
},
|
||||
// Valid case: Object literal not used with protobuf type
|
||||
{
|
||||
code: `
|
||||
interface MyInterface {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
const obj: MyInterface = {
|
||||
id: 123,
|
||||
name: 'test'
|
||||
};
|
||||
`,
|
||||
},
|
||||
// Valid case: Using object literal for non-protobuf import
|
||||
{
|
||||
code: `
|
||||
import { SomeType } from '@some/other/package';
|
||||
|
||||
const obj: SomeType = {
|
||||
id: 123,
|
||||
name: 'test'
|
||||
};
|
||||
`,
|
||||
},
|
||||
// Valid case: Regular function call with object literal (should not be flagged)
|
||||
{
|
||||
code: `
|
||||
import { State } from '@shared/proto/state';
|
||||
|
||||
// This should not be flagged because it's a regular function call
|
||||
// not directly tied to a protobuf type
|
||||
process({
|
||||
id: 123,
|
||||
name: 'test',
|
||||
data: { nested: true }
|
||||
});
|
||||
`,
|
||||
},
|
||||
],
|
||||
invalid: [
|
||||
// Invalid case: Using object literal with imported protobuf type
|
||||
{
|
||||
code: `
|
||||
import { State } from '@shared/proto/state';
|
||||
|
||||
const state: State = {
|
||||
stateJson: '{"apiConfig":{"provider":"anthropic","model":"claude-3-haiku"}}'
|
||||
};
|
||||
`,
|
||||
output: `
|
||||
import { State } from '@shared/proto/state';
|
||||
|
||||
const state: State = State.create({
|
||||
stateJson: '{"apiConfig":{"provider":"anthropic","model":"claude-3-haiku"}}'
|
||||
});
|
||||
`,
|
||||
errors: [{ messageId: "useProtobufMethod" }],
|
||||
},
|
||||
// Invalid case: Using object literal with namespaced protobuf type
|
||||
{
|
||||
code: `
|
||||
import * as stateProto from '@shared/proto/state';
|
||||
|
||||
const state: stateProto.State = {
|
||||
stateJson: '{"apiConfig":{"provider":"anthropic","model":"claude-3-haiku"}}'
|
||||
};
|
||||
`,
|
||||
output: `
|
||||
import * as stateProto from '@shared/proto/state';
|
||||
|
||||
const state: stateProto.State = stateProto.State.create({
|
||||
stateJson: '{"apiConfig":{"provider":"anthropic","model":"claude-3-haiku"}}'
|
||||
});
|
||||
`,
|
||||
errors: [{ messageId: "useProtobufMethodGeneric" }],
|
||||
},
|
||||
// Invalid case: Using object literal in a return statement (with protobuf return type)
|
||||
{
|
||||
code: `
|
||||
import { ChatSettings } from '@shared/proto/state';
|
||||
|
||||
function createSettings(): ChatSettings {
|
||||
return {
|
||||
mode: 0,
|
||||
preferredLanguage: 'en',
|
||||
openAiReasoningEffort: 'thorough'
|
||||
};
|
||||
}
|
||||
`,
|
||||
output: `
|
||||
import { ChatSettings } from '@shared/proto/state';
|
||||
|
||||
function createSettings(): ChatSettings {
|
||||
return ChatSettings.create({
|
||||
mode: 0,
|
||||
preferredLanguage: 'en',
|
||||
openAiReasoningEffort: 'thorough'
|
||||
});
|
||||
}
|
||||
`,
|
||||
errors: [{ messageId: "useProtobufMethod" }],
|
||||
},
|
||||
// Invalid case: Using object literal in a function parameter (with protobuf types imported)
|
||||
{
|
||||
code: `
|
||||
import { ChatContent } from '@shared/proto/state';
|
||||
|
||||
function processContent(content: ChatContent) {
|
||||
// process the content
|
||||
}
|
||||
|
||||
processContent({
|
||||
message: 'Hello, this is a test message',
|
||||
images: ['image1.png', 'image2.jpg'],
|
||||
files: ['file1.txt', 'file2.pdf']
|
||||
});
|
||||
`,
|
||||
output: `
|
||||
import { ChatContent } from '@shared/proto/state';
|
||||
|
||||
function processContent(content: ChatContent) {
|
||||
// process the content
|
||||
}
|
||||
|
||||
processContent(ChatContent.create({
|
||||
message: 'Hello, this is a test message',
|
||||
images: ['image1.png', 'image2.jpg'],
|
||||
files: ['file1.txt', 'file2.pdf']
|
||||
}));
|
||||
`,
|
||||
errors: [{ messageId: "useProtobufMethodGeneric" }],
|
||||
},
|
||||
// Invalid case: Using object literal in assignment expression
|
||||
{
|
||||
code: `
|
||||
import { State } from '@shared/proto/state';
|
||||
|
||||
let state: State;
|
||||
state = {
|
||||
stateJson: '{"apiConfig":{"provider":"anthropic","model":"claude-3-haiku"}}'
|
||||
};
|
||||
`,
|
||||
output: `
|
||||
import { State } from '@shared/proto/state';
|
||||
|
||||
let state: State;
|
||||
state = State.create({
|
||||
stateJson: '{"apiConfig":{"provider":"anthropic","model":"claude-3-haiku"}}'
|
||||
});
|
||||
`,
|
||||
errors: [{ messageId: "useProtobufMethod" }],
|
||||
},
|
||||
// Test with custom protobufPackages option
|
||||
{
|
||||
code: `
|
||||
import { CustomProto } from 'custom/proto/package';
|
||||
|
||||
const obj: CustomProto = {
|
||||
field1: 'value',
|
||||
field2: 123
|
||||
};
|
||||
`,
|
||||
output: `
|
||||
import { CustomProto } from 'custom/proto/package';
|
||||
|
||||
const obj: CustomProto = CustomProto.create({
|
||||
field1: 'value',
|
||||
field2: 123
|
||||
});
|
||||
`,
|
||||
options: [{ protobufPackages: ["custom/proto"] }],
|
||||
errors: [{ messageId: "useProtobufMethod" }],
|
||||
},
|
||||
],
|
||||
})
|
||||
@@ -1,22 +0,0 @@
|
||||
// eslint-rules/index.js
|
||||
const noProtobufObjectLiterals = require("./no-protobuf-object-literals")
|
||||
const noGrpcClientObjectLiterals = require("./no-grpc-client-object-literals")
|
||||
const noDirectVscodeApi = require("./no-direct-vscode-api")
|
||||
|
||||
module.exports = {
|
||||
rules: {
|
||||
"no-protobuf-object-literals": noProtobufObjectLiterals,
|
||||
"no-grpc-client-object-literals": noGrpcClientObjectLiterals,
|
||||
"no-direct-vscode-api": noDirectVscodeApi,
|
||||
},
|
||||
configs: {
|
||||
recommended: {
|
||||
plugins: ["local"],
|
||||
rules: {
|
||||
"local/no-protobuf-object-literals": "error",
|
||||
"local/no-grpc-client-object-literals": "error",
|
||||
"local/no-direct-vscode-api": "warn",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -1,164 +0,0 @@
|
||||
const { ESLintUtils } = require("@typescript-eslint/utils")
|
||||
const path = require("path")
|
||||
|
||||
const createRule = ESLintUtils.RuleCreator((name) => `https://cline.bot/eslint-rules/${name}`)
|
||||
|
||||
// Configuration of disallowed VSCode APIs and their recommended alternatives
|
||||
const disallowedApis = {
|
||||
"vscode.postMessage": {
|
||||
messageId: "useGrpcClient",
|
||||
},
|
||||
"vscode.workspace.fs.stat": {
|
||||
messageId: "useFsUtils",
|
||||
},
|
||||
"vscode.workspace.workspaceFolders": {
|
||||
messageId: "useHostBridge",
|
||||
},
|
||||
"vscode.workspace.asRelativePath": {
|
||||
messageId: "usePathUtils",
|
||||
},
|
||||
"vscode.workspace.getWorkspaceFolder": {
|
||||
messageId: "usePathUtils",
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = createRule({
|
||||
name: "no-direct-vscode-api",
|
||||
meta: {
|
||||
type: "problem",
|
||||
docs: {
|
||||
description:
|
||||
"Disallow direct VSCode API usage in favor of Cline's abstraction layers, except in src/hosts/vscode and standalone/runtime-files directories",
|
||||
recommended: "error",
|
||||
},
|
||||
messages: {
|
||||
useGrpcClient:
|
||||
"Use gRPC service clients instead of vscode.postMessage().\n" +
|
||||
"Example: AccountServiceClient.methodName(RequestType.create({...})) instead of vscode.postMessage({type: '...'}).\n" +
|
||||
"Found: {{code}}",
|
||||
useFsUtils:
|
||||
"Use utilities in @/utils/fs instead of vscode.workspace.fs.stat.\n" +
|
||||
"Example: import { isDirectory } from '@/utils/fs' or use the file system methods from the host bridge provider.\n" +
|
||||
"Found: {{code}}",
|
||||
useHostBridge:
|
||||
"Use getHostBridgeProvider().workspaceClient.getWorkspacePaths({}) instead of vscode.workspace.workspaceFolders.\n" +
|
||||
"This provides a consistent abstraction across VSCode and standalone environments.\n" +
|
||||
"Found: {{code}}",
|
||||
usePathUtils:
|
||||
"Use path utilities from @/utils/path instead of direct VSCode workspace path methods.\n" +
|
||||
"This provides consistent path handling across different environments.\n" +
|
||||
"Found: {{code}}",
|
||||
},
|
||||
schema: [],
|
||||
},
|
||||
defaultOptions: [],
|
||||
|
||||
create(context) {
|
||||
// Check if current file is in an exception directory or is grpc-client-base.ts
|
||||
const filename = context.filename
|
||||
const isGrpcClientBase = path.basename(filename) === "grpc-client-base.ts"
|
||||
|
||||
// Skip checking files in src/hosts/vscode or standalone/runtime-files
|
||||
const isExceptionDirectory = filename.includes("/src/hosts/vscode/") || filename.includes("/standalone/runtime-files/")
|
||||
|
||||
// Pattern for checking memberExpressions like vscode.workspace.fs.stat
|
||||
function checkMemberExpression(node) {
|
||||
// Skip if this file is in an exception directory or is grpc-client-base.ts
|
||||
if (isGrpcClientBase || isExceptionDirectory) {
|
||||
return
|
||||
}
|
||||
|
||||
// For handling nested properties like vscode.workspace.fs.stat
|
||||
function getFullPropertyPath(node) {
|
||||
if (node.type !== "MemberExpression") {
|
||||
return node.name || ""
|
||||
}
|
||||
|
||||
const objectPart = getFullPropertyPath(node.object)
|
||||
const propertyPart = node.property.name || ""
|
||||
|
||||
return objectPart ? `${objectPart}.${propertyPart}` : propertyPart
|
||||
}
|
||||
|
||||
// Check if the expression matches one of our disallowed patterns
|
||||
if (node.object && node.object.type === "Identifier" && node.object.name === "vscode") {
|
||||
const fullPath = `vscode.${node.property.name}`
|
||||
checkDisallowedApi(fullPath, node)
|
||||
}
|
||||
// Handle nested expressions like vscode.workspace.fs.stat
|
||||
else if (node.object && node.object.type === "MemberExpression") {
|
||||
const fullPath = getFullPropertyPath(node)
|
||||
|
||||
// Only proceed if it starts with vscode
|
||||
if (fullPath.startsWith("vscode.")) {
|
||||
checkDisallowedApi(fullPath, node)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if an expression matches a disallowed API and report if it does
|
||||
function checkDisallowedApi(expressionPath, node) {
|
||||
// Check exact matches
|
||||
if (disallowedApis[expressionPath]) {
|
||||
reportViolation(expressionPath, node)
|
||||
return
|
||||
}
|
||||
|
||||
// Check prefix matches (for nested properties)
|
||||
for (const disallowedApi in disallowedApis) {
|
||||
// For direct property access like vscode.workspace.workspaceFolders
|
||||
if (expressionPath === disallowedApi) {
|
||||
reportViolation(disallowedApi, node)
|
||||
return
|
||||
}
|
||||
|
||||
// For method calls like vscode.workspace.asRelativePath(...)
|
||||
if (expressionPath.startsWith(`${disallowedApi}.`) || expressionPath.startsWith(`${disallowedApi}(`)) {
|
||||
reportViolation(disallowedApi, node)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Report a violation with the appropriate message
|
||||
function reportViolation(disallowedApi, node) {
|
||||
const sourceCode = context.sourceCode
|
||||
const config = disallowedApis[disallowedApi]
|
||||
|
||||
// For method calls, get the whole call expression
|
||||
let reportNode = node
|
||||
let parentNode = sourceCode.getAncestors(node).pop()
|
||||
if (parentNode && parentNode.type === "CallExpression" && parentNode.callee === node) {
|
||||
reportNode = parentNode
|
||||
}
|
||||
|
||||
const callText = sourceCode.getText(reportNode).trim()
|
||||
|
||||
context.report({
|
||||
node: reportNode,
|
||||
messageId: config.messageId,
|
||||
data: {
|
||||
code: callText,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
// Detect basic member expressions (e.g., vscode.postMessage)
|
||||
MemberExpression(node) {
|
||||
checkMemberExpression(node)
|
||||
},
|
||||
|
||||
// Detect property access through destructuring
|
||||
VariableDeclarator(node) {
|
||||
// Skip if this file is in an exception directory or is grpc-client-base.ts
|
||||
if (isGrpcClientBase || isExceptionDirectory) {
|
||||
return
|
||||
}
|
||||
|
||||
// Destructuring pattern checks removed as developers don't use the API this way
|
||||
// They always use direct imports: import * as vscode from "vscode" and direct access: vscode.thing.foo
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -1,216 +0,0 @@
|
||||
const { ESLintUtils } = require("@typescript-eslint/utils")
|
||||
|
||||
const createRule = ESLintUtils.RuleCreator((name) => `https://cline.bot/eslint-rules/${name}`)
|
||||
|
||||
module.exports = createRule({
|
||||
name: "no-grpc-client-object-literals",
|
||||
meta: {
|
||||
type: "problem",
|
||||
docs: {
|
||||
description:
|
||||
"Enforce using .create() or .fromPartial() for gRPC service client parameters instead of object literals",
|
||||
recommended: "error",
|
||||
},
|
||||
messages: {
|
||||
useProtobufMethod:
|
||||
"Use the appropriate protobuf .create() or .fromPartial() method instead of " +
|
||||
"object literal for gRPC client parameters.\n" +
|
||||
"Found: {{code}}\n" +
|
||||
"gRPC client methods should always receive properly created protobuf objects.",
|
||||
},
|
||||
schema: [],
|
||||
},
|
||||
defaultOptions: [],
|
||||
|
||||
create(context) {
|
||||
// Check if a name matches the gRPC service client pattern using regex
|
||||
// Must start with an uppercase letter and end with ServiceClient
|
||||
const isGrpcServiceClient = (name) => {
|
||||
return typeof name === "string" && /^[A-Z].*ServiceClient$/.test(name)
|
||||
}
|
||||
|
||||
const safeObjectExpressions = new Map() // Track object expressions in create/fromPartial calls
|
||||
|
||||
return {
|
||||
// Skip object literals inside create() or fromPartial() method calls
|
||||
CallExpression(node) {
|
||||
if (
|
||||
node.callee &&
|
||||
node.callee.type === "MemberExpression" &&
|
||||
(node.callee.property.name === "create" || node.callee.property.name === "fromPartial") &&
|
||||
node.arguments.length > 0 &&
|
||||
node.arguments[0].type === "ObjectExpression"
|
||||
) {
|
||||
// Track this object expression as being used with create/fromPartial
|
||||
safeObjectExpressions.set(node.arguments[0], { isProblematic: false })
|
||||
}
|
||||
},
|
||||
|
||||
// Track create/fromPartial calls that contain nested object literals
|
||||
"CallExpression[callee.type='MemberExpression'][callee.property.name=/^(create|fromPartial)$/]"(node) {
|
||||
if (node.arguments.length > 0 && node.arguments[0].type === "ObjectExpression") {
|
||||
// Track problematic nested object literals
|
||||
const nestedObjectLiterals = new Map() // Map of object expressions to their containing property paths
|
||||
|
||||
// Search for nested object literals
|
||||
const queue = [
|
||||
...node.arguments[0].properties.map((prop) => ({
|
||||
property: prop,
|
||||
path: prop.key && prop.key.name ? prop.key.name : "unknown",
|
||||
})),
|
||||
]
|
||||
|
||||
while (queue.length > 0) {
|
||||
const { property, path } = queue.shift()
|
||||
|
||||
// Skip spread elements
|
||||
if (property.type !== "Property") continue
|
||||
|
||||
// If this is an object literal, mark it as problematic
|
||||
if (property.value.type === "ObjectExpression") {
|
||||
nestedObjectLiterals.set(property.value, path)
|
||||
|
||||
// Add nested properties to queue
|
||||
queue.push(
|
||||
...property.value.properties.map((prop) => ({
|
||||
property: prop,
|
||||
path: `${path}.${prop.key && prop.key.name ? prop.key.name : "unknown"}`,
|
||||
})),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// For each problematic nested object, track it with its path
|
||||
nestedObjectLiterals.forEach((path, objectExpr) => {
|
||||
safeObjectExpressions.set(objectExpr, {
|
||||
isProblematic: true,
|
||||
path: path,
|
||||
parentNode: node,
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
// Check calls to gRPC service clients
|
||||
"CallExpression[callee.type='MemberExpression']"(node) {
|
||||
// Get the object (left side) of the member expression
|
||||
const callee = node.callee
|
||||
if (callee.object && callee.object.type === "Identifier") {
|
||||
const objectName = callee.object.name
|
||||
|
||||
// Check if this is a call to one of our gRPC service clients
|
||||
if (isGrpcServiceClient(objectName)) {
|
||||
// Only check the first argument of gRPC service client calls
|
||||
if (node.arguments.length > 0) {
|
||||
const arg = node.arguments[0] // Only check the first parameter
|
||||
if (arg.type === "ObjectExpression" && !safeObjectExpressions.has(arg)) {
|
||||
// This is an object literal being passed directly to a gRPC client
|
||||
const sourceCode = context.getSourceCode()
|
||||
const callText = sourceCode.getText(node).trim()
|
||||
|
||||
context.report({
|
||||
node: arg,
|
||||
messageId: "useProtobufMethod",
|
||||
data: {
|
||||
code: callText,
|
||||
},
|
||||
})
|
||||
} else if (arg.type === "ObjectExpression") {
|
||||
// Search for nested object literals that aren't protected
|
||||
const queue = [...arg.properties]
|
||||
while (queue.length > 0) {
|
||||
const property = queue.shift()
|
||||
|
||||
// Skip spread elements
|
||||
if (property.type !== "Property") continue
|
||||
|
||||
// Check value
|
||||
if (
|
||||
property.value.type === "ObjectExpression" &&
|
||||
!safeObjectExpressions.has(property.value)
|
||||
) {
|
||||
// Found a nested object literal
|
||||
const sourceCode = context.getSourceCode()
|
||||
const propertyText = sourceCode.getText(property).trim()
|
||||
|
||||
context.report({
|
||||
node: property.value,
|
||||
messageId: "useProtobufMethod",
|
||||
data: {
|
||||
code: `${objectName}.${callee.property.name}(... ${propertyText} ...)`,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Add any nested properties to the queue
|
||||
if (property.value.type === "ObjectExpression") {
|
||||
queue.push(...property.value.properties)
|
||||
}
|
||||
}
|
||||
} else if (arg.type === "Identifier") {
|
||||
// This is a variable - check if it references a problematic protobuf object
|
||||
const varName = arg.name
|
||||
const sourceCode = context.getSourceCode()
|
||||
const scope = sourceCode.getScope(node)
|
||||
|
||||
// Find the variable declaration
|
||||
const variable = scope.variables.find((v) => v.name === varName)
|
||||
if (variable && variable.references && variable.references.length > 0) {
|
||||
// Look for definitions
|
||||
const def = variable.defs.find(
|
||||
(d) => d.node && d.node.type === "VariableDeclarator" && d.node.init,
|
||||
)
|
||||
|
||||
if (
|
||||
def &&
|
||||
def.node.init.type === "CallExpression" &&
|
||||
def.node.init.callee.type === "MemberExpression" &&
|
||||
(def.node.init.callee.property.name === "create" ||
|
||||
def.node.init.callee.property.name === "fromPartial")
|
||||
) {
|
||||
// Flag if we find problematic nested object literals in this create/fromPartial call
|
||||
const callText = sourceCode.getText(node).trim()
|
||||
const initCallText = sourceCode.getText(def.node.init).trim()
|
||||
|
||||
// Check for nested object literals in init node
|
||||
let foundNestedLiteral = false
|
||||
if (
|
||||
def.node.init.arguments.length > 0 &&
|
||||
def.node.init.arguments[0].type === "ObjectExpression"
|
||||
) {
|
||||
// Find any nested object literals
|
||||
const queue = [...def.node.init.arguments[0].properties]
|
||||
while (queue.length > 0 && !foundNestedLiteral) {
|
||||
const property = queue.shift()
|
||||
|
||||
// Skip spread elements
|
||||
if (property.type !== "Property") continue
|
||||
|
||||
if (property.value.type === "ObjectExpression") {
|
||||
foundNestedLiteral = true
|
||||
|
||||
context.report({
|
||||
node,
|
||||
messageId: "useProtobufMethod",
|
||||
data: {
|
||||
code: `${callText} - using request created with nested object literal at: ${property.key.name}`,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Add any nested properties to the queue
|
||||
if (property.value.type === "ObjectExpression") {
|
||||
queue.push(...property.value.properties)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -1,556 +0,0 @@
|
||||
const { ESLintUtils } = require("@typescript-eslint/utils")
|
||||
|
||||
const createRule = ESLintUtils.RuleCreator((name) => `https://cline.bot/eslint-rules/${name}`)
|
||||
|
||||
module.exports = createRule({
|
||||
name: "no-protobuf-object-literals",
|
||||
meta: {
|
||||
type: "problem",
|
||||
docs: {
|
||||
description: "Enforce using .create() or .fromPartial() for protobuf objects instead of object literals",
|
||||
recommended: "error",
|
||||
},
|
||||
fixable: "code",
|
||||
messages: {
|
||||
useProtobufMethod:
|
||||
"Use {{typeName}}.create() or {{typeName}}.fromPartial() instead of " +
|
||||
"object literal for protobuf type from @shared/proto\n" +
|
||||
"Found: {{code}}\n Suggestion: " +
|
||||
"{{typeName}}.create({{objectContent}})",
|
||||
useProtobufMethodGeneric:
|
||||
"Use .create() or .fromPartial() instead of object literal for protobuf " +
|
||||
"type from @shared/proto\n Found: {{code}}",
|
||||
},
|
||||
schema: [
|
||||
{
|
||||
type: "object",
|
||||
properties: {
|
||||
protobufPackages: {
|
||||
type: "array",
|
||||
items: { type: "string" },
|
||||
default: ["shared/proto/"],
|
||||
},
|
||||
},
|
||||
additionalProperties: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
defaultOptions: [{ protobufPackages: ["shared/proto/"] }],
|
||||
|
||||
create(context, [options]) {
|
||||
const protobufPackages = options.protobufPackages
|
||||
const protobufImports = new Set() // Set of imported protobuf types
|
||||
const protobufNamespaceImports = new Set() // For namespace imports like "import * as proto"
|
||||
const safeObjectExpressions = new Set() // Track object expressions in create/fromPartial calls
|
||||
|
||||
return {
|
||||
// Skip object literals inside create() or fromPartial() method calls
|
||||
CallExpression(node) {
|
||||
if (
|
||||
node.callee &&
|
||||
node.callee.type === "MemberExpression" &&
|
||||
(node.callee.property.name === "create" || node.callee.property.name === "fromPartial") &&
|
||||
node.arguments.length > 0 &&
|
||||
node.arguments[0].type === "ObjectExpression"
|
||||
) {
|
||||
// Track this object expression as being used with create/fromPartial
|
||||
safeObjectExpressions.add(node.arguments[0])
|
||||
}
|
||||
},
|
||||
|
||||
// Track imports from protobuf packages
|
||||
ImportDeclaration(node) {
|
||||
const packageName = node.source.value
|
||||
|
||||
if (matchesProtobufPackage(packageName, protobufPackages)) {
|
||||
// This is a protobuf package.
|
||||
node.specifiers.forEach((spec) => {
|
||||
if (spec.type === "ImportSpecifier") {
|
||||
// import { MyRequest } from '@shared/proto'
|
||||
protobufImports.add(spec.imported.name)
|
||||
} else if (spec.type === "ImportNamespaceSpecifier") {
|
||||
// import * as proto from '@shared/proto'
|
||||
protobufNamespaceImports.add(spec.local.name)
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
// Check variable declarations with type annotations
|
||||
"VariableDeclarator > ObjectExpression"(node) {
|
||||
// Skip if this is inside a create/fromPartial call
|
||||
if (safeObjectExpressions.has(node)) {
|
||||
return
|
||||
}
|
||||
|
||||
// Found object literal in variable declaration
|
||||
const declarator = node.parent
|
||||
|
||||
if (declarator.id && declarator.id.typeAnnotation) {
|
||||
const typeName = getTypeName(declarator.id.typeAnnotation.typeAnnotation)
|
||||
if (typeName) {
|
||||
// Check if it's a direct protobuf import
|
||||
if (protobufImports.has(typeName)) {
|
||||
//console.log('🚨 VIOLATION: Using object literal for protobuf type:', typeName);
|
||||
const sourceCode = context.getSourceCode()
|
||||
const declaratorText = sourceCode.getText(declarator)
|
||||
const objectText = sourceCode.getText(node)
|
||||
|
||||
context.report({
|
||||
node,
|
||||
messageId: "useProtobufMethod",
|
||||
data: {
|
||||
typeName,
|
||||
code: declaratorText,
|
||||
objectContent: objectText,
|
||||
},
|
||||
fix(fixer) {
|
||||
// Replace the object literal with Type.create() call
|
||||
return fixer.replaceText(node, `${typeName}.create(${objectText})`)
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Check if it's a namespaced protobuf type (e.g., proto.MyRequest)
|
||||
if (isNamespacedProtobufType(protobufNamespaceImports, typeName)) {
|
||||
//console.log('🚨 VIOLATION: Using object literal for namespaced protobuf type:', typeName);
|
||||
const sourceCode = context.getSourceCode()
|
||||
const declaratorText = sourceCode.getText(declarator)
|
||||
context.report({
|
||||
node,
|
||||
messageId: "useProtobufMethodGeneric",
|
||||
data: { code: declaratorText },
|
||||
fix(fixer) {
|
||||
// For namespaced types, use the full type name to call create()
|
||||
return fixer.replaceText(node, `${typeName}.create(${sourceCode.getText(node)})`)
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Check assignment expressions
|
||||
"AssignmentExpression > ObjectExpression"(node) {
|
||||
// Skip if this is inside a create/fromPartial call
|
||||
if (safeObjectExpressions.has(node)) {
|
||||
return
|
||||
}
|
||||
|
||||
const assignment = node.parent
|
||||
|
||||
// For assignment to variables without inline type annotation
|
||||
if (assignment.left && assignment.right === node) {
|
||||
let typeName = null
|
||||
|
||||
// Check if there's a typeAnnotation directly on the left
|
||||
if (assignment.left.typeAnnotation) {
|
||||
typeName = getTypeName(assignment.left.typeAnnotation.typeAnnotation)
|
||||
}
|
||||
// Otherwise try to infer from the variable name if it's a simple identifier
|
||||
else if (assignment.left.type === "Identifier") {
|
||||
const varName = assignment.left.name
|
||||
// Check variable declarations in the current scope
|
||||
const sourceCode = context.getSourceCode()
|
||||
const scope = sourceCode.getScope(node)
|
||||
const variable = scope.variables.find((v) => v.name === varName)
|
||||
if (variable && variable.defs.length > 0) {
|
||||
const def = variable.defs[0]
|
||||
if (def.node.id && def.node.id.typeAnnotation) {
|
||||
typeName = getTypeName(def.node.id.typeAnnotation.typeAnnotation)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (typeName && protobufImports.has(typeName)) {
|
||||
//console.log('🚨 VIOLATION: Using object literal in assignment for protobuf type:', typeName);
|
||||
const sourceCode = context.getSourceCode()
|
||||
const assignmentText = sourceCode.getText(assignment.left) + " = "
|
||||
const objectText = sourceCode.getText(node)
|
||||
|
||||
context.report({
|
||||
node,
|
||||
messageId: "useProtobufMethod",
|
||||
data: {
|
||||
typeName,
|
||||
code: assignmentText + "{",
|
||||
objectContent: objectText,
|
||||
},
|
||||
fix(fixer) {
|
||||
// Replace the object literal with Type.create() call in assignments
|
||||
return fixer.replaceText(node, `${typeName}.create(${objectText})`)
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Check return statements
|
||||
"ReturnStatement > ObjectExpression"(node) {
|
||||
// Skip if this is inside a create/fromPartial call
|
||||
if (safeObjectExpressions.has(node)) {
|
||||
return
|
||||
}
|
||||
|
||||
// Find the parent function to get its return type
|
||||
const functionNode = findParentFunction(node)
|
||||
if (!functionNode) {
|
||||
return
|
||||
}
|
||||
|
||||
// Try to get the return type using our enhanced helper
|
||||
const sourceCode = context.getSourceCode()
|
||||
let returnTypeName = getFunctionReturnType(functionNode, sourceCode)
|
||||
|
||||
// For async functions with Promise<Type> return type, extract the inner type
|
||||
if (returnTypeName && returnTypeName.startsWith("Promise<") && returnTypeName.endsWith(">")) {
|
||||
returnTypeName = returnTypeName.slice(8, -1)
|
||||
}
|
||||
|
||||
// Check if the return type is a protobuf type
|
||||
if (returnTypeName) {
|
||||
if (protobufImports.has(returnTypeName)) {
|
||||
//console.log('🚨 VIOLATION: Return type is a protobuf type:', returnTypeName);
|
||||
const sourceCode = context.getSourceCode()
|
||||
const returnText = sourceCode.getText(node.parent)
|
||||
context.report({
|
||||
node,
|
||||
messageId: "useProtobufMethod",
|
||||
data: {
|
||||
typeName: returnTypeName,
|
||||
code: returnText,
|
||||
objectContent: sourceCode.getText(node),
|
||||
},
|
||||
fix(fixer) {
|
||||
// Replace the object literal with Type.create() call in return statements
|
||||
return fixer.replaceText(node, `${returnTypeName}.create(${sourceCode.getText(node)})`)
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Check if it's a namespaced protobuf type
|
||||
if (isNamespacedProtobufType(protobufNamespaceImports, returnTypeName)) {
|
||||
const sourceCode = context.getSourceCode()
|
||||
const returnText = sourceCode.getText(node.parent)
|
||||
//console.log('🚨 VIOLATION: Return type is a namespaced protobuf type:', returnTypeName);
|
||||
context.report({
|
||||
node,
|
||||
messageId: "useProtobufMethodGeneric",
|
||||
data: { code: returnText },
|
||||
fix(fixer) {
|
||||
// For namespaced types in return statements, we need to extract the full type name
|
||||
const objectCode = sourceCode.getText(node)
|
||||
// Since we may not know the exact type, we'll use the more generic namespaced type
|
||||
return fixer.replaceText(node, `${returnTypeName}.create(${objectCode})`)
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Final fallback - if there are any protobuf imports and the function signature
|
||||
// mentions a return type that matches one of the imported types
|
||||
const functionText = functionNode ? sourceCode.getText(functionNode) : ""
|
||||
|
||||
for (const protoType of protobufImports) {
|
||||
// Use more precise regex to match return type patterns specifically
|
||||
// Rather than just checking if the type name appears anywhere in the signature
|
||||
const returnTypeRegex = new RegExp(
|
||||
// Match arrow function return type
|
||||
`=>\\s*:?\\s*${protoType}\\b|` +
|
||||
// Match function declaration return type
|
||||
`\\)\\s*:?\\s*${protoType}\\b|` +
|
||||
// Match Promise return type
|
||||
`\\)\\s*:?\\s*Promise<\\s*${protoType}\\s*>|` +
|
||||
// Match function type in variable declaration
|
||||
`:\\s*\\(.*\\)\\s*=>\\s*${protoType}\\b`,
|
||||
)
|
||||
|
||||
if (returnTypeRegex.test(functionText)) {
|
||||
const returnText = sourceCode.getText(node.parent)
|
||||
//console.log('🚨 VIOLATION: regex matched protobuf type:', functionText);
|
||||
context.report({
|
||||
node,
|
||||
messageId: "useProtobufMethod",
|
||||
data: {
|
||||
typeName: protoType,
|
||||
code: returnText,
|
||||
objectContent: sourceCode.getText(node),
|
||||
},
|
||||
fix(fixer) {
|
||||
// Replace the object literal with Type.create() call
|
||||
return fixer.replaceText(node, `${protoType}.create(${sourceCode.getText(node)})`)
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
// Check for namespace imports too
|
||||
for (const namespace of protobufNamespaceImports) {
|
||||
// Similar to above, but for namespaced types
|
||||
const namespaceReturnTypeRegex = new RegExp(
|
||||
// Match arrow function return type
|
||||
`=>\\s*:?\\s*${namespace}\\.\\w+\\b|` +
|
||||
// Match function declaration return type
|
||||
`\\)\\s*:?\\s*${namespace}\\.\\w+\\b|` +
|
||||
// Match Promise return type
|
||||
`\\)\\s*:?\\s*Promise<\\s*${namespace}\\.\\w+\\s*>|` +
|
||||
// Match function type in variable declaration
|
||||
`:\\s*\\(.*\\)\\s*=>\\s*${namespace}\\.\\w+\\b`,
|
||||
)
|
||||
|
||||
if (namespaceReturnTypeRegex.test(functionText)) {
|
||||
const returnText = sourceCode.getText(node.parent)
|
||||
//console.log('🚨 VIOLATION: regex matched namespaced protobuf type:', functionText, "namespace:", namespace);
|
||||
context.report({
|
||||
node,
|
||||
messageId: "useProtobufMethodGeneric",
|
||||
data: { code: returnText },
|
||||
fix(fixer) {
|
||||
// For namespaced types based on function signature patterns
|
||||
// Extract the namespace and type from the function text using more precise patterns
|
||||
const match = functionText.match(
|
||||
new RegExp(
|
||||
// Match return type patterns more precisely
|
||||
`\\)\\s*:?\\s*(${namespace}\\.[\\w]+)\\b|` + // Function declaration
|
||||
`=>\\s*:?\\s*(${namespace}\\.[\\w]+)\\b|` + // Arrow function
|
||||
`Promise<\\s*(${namespace}\\.[\\w]+)\\s*>`, // Promise wrapped
|
||||
),
|
||||
)
|
||||
if (match) {
|
||||
const fullType = match[1] || match[2]
|
||||
return fixer.replaceText(node, `${fullType}.create(${sourceCode.getText(node)})`)
|
||||
}
|
||||
// Fallback - we can't determine the exact type, but we know it's from the namespace
|
||||
// Use a namespace-based approach
|
||||
return fixer.replaceText(node, `${namespace}.create(${sourceCode.getText(node)})`)
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Check function call arguments (more selective approach)
|
||||
"CallExpression > ObjectExpression"(node) {
|
||||
// Skip if this is inside a create/fromPartial call
|
||||
if (safeObjectExpressions.has(node)) {
|
||||
return
|
||||
}
|
||||
|
||||
// We need to be more selective to avoid false positives
|
||||
// Only warn if:
|
||||
// 1. The function is called on a protobuf namespace
|
||||
// 2. The call argument has a type annotation that matches a protobuf type
|
||||
// 3. The call is to a function that we know takes a protobuf type
|
||||
|
||||
// Check if it's a call on a protobuf namespace
|
||||
if (
|
||||
node.parent.callee &&
|
||||
node.parent.callee.type === "MemberExpression" &&
|
||||
node.parent.callee.object.type === "Identifier"
|
||||
) {
|
||||
const namespace = node.parent.callee.object.name
|
||||
if (protobufNamespaceImports.has(namespace)) {
|
||||
const sourceCode = context.getSourceCode()
|
||||
const callText = sourceCode.getText(node.parent)
|
||||
//console.log('🚨 VIOLATION: Check function call arguments object literal:', callText);
|
||||
context.report({
|
||||
node,
|
||||
messageId: "useProtobufMethodGeneric",
|
||||
data: { code: callText },
|
||||
fix(fixer) {
|
||||
// For calls on a protobuf namespace
|
||||
const memberExpr = node.parent.callee
|
||||
// Try to determine if this is calling a method that expects a specific type
|
||||
const methodName = memberExpr.property.name
|
||||
|
||||
// If method name looks like 'create' + Type, we can infer the type
|
||||
const possibleTypeName = methodName.replace(/^create/, "")
|
||||
|
||||
// Check if namespace has a type with this name
|
||||
// Since we can't directly check at lint time, we'll use the namespace + inferred type
|
||||
if (possibleTypeName && possibleTypeName !== methodName) {
|
||||
return fixer.replaceText(
|
||||
node,
|
||||
`${namespace}.${possibleTypeName}.create(${sourceCode.getText(node)})`,
|
||||
)
|
||||
}
|
||||
|
||||
// Fallback - use a more generic approach with namespace
|
||||
return fixer.replaceText(node, `${namespace}.create(${sourceCode.getText(node)})`)
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// For regular function calls with object literals, check if there are protobuf imports
|
||||
// and if the function might expect a protobuf type
|
||||
if (node.parent.callee) {
|
||||
// This is a more permissive check to catch cases like processContent({ ... })
|
||||
// which might be passing a protobuf type
|
||||
const sourceCode = context.getSourceCode()
|
||||
const scope = sourceCode.getScope(node)
|
||||
|
||||
// Try to find the function definition
|
||||
if (node.parent.callee.type === "Identifier") {
|
||||
const functionName = node.parent.callee.name
|
||||
const variable = scope.variables.find((v) => v.name === functionName)
|
||||
|
||||
// If we found the function and it has parameter type annotations
|
||||
// that match protobuf types, flag it
|
||||
if (variable && variable.defs.length > 0) {
|
||||
const def = variable.defs[0]
|
||||
if (def.node.params && node.parent.arguments.indexOf(node) < def.node.params.length) {
|
||||
const param = def.node.params[node.parent.arguments.indexOf(node)]
|
||||
if (param.typeAnnotation) {
|
||||
const typeName = getTypeName(param.typeAnnotation.typeAnnotation)
|
||||
if (
|
||||
typeName &&
|
||||
(protobufImports.has(typeName) ||
|
||||
isNamespacedProtobufType(protobufNamespaceImports, typeName))
|
||||
) {
|
||||
const callText = sourceCode.getText(node.parent)
|
||||
//console.log('🚨 VIOLATION: Function call arguments object literal:', callText);
|
||||
context.report({
|
||||
node,
|
||||
messageId: "useProtobufMethodGeneric",
|
||||
data: { code: callText },
|
||||
fix(fixer) {
|
||||
// For function calls with protobuf type parameters
|
||||
return fixer.replaceText(node, `${typeName}.create(${sourceCode.getText(node)})`)
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
// Helper functions
|
||||
function getTypeName(typeAnnotation) {
|
||||
if (!typeAnnotation) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (typeAnnotation.type === "TSTypeReference") {
|
||||
if (typeAnnotation.typeName.type === "Identifier") {
|
||||
return typeAnnotation.typeName.name
|
||||
} else if (typeAnnotation.typeName.type === "TSQualifiedName") {
|
||||
// Handle namespaced types like proto.MyRequest
|
||||
return `${typeAnnotation.typeName.left.name}.${typeAnnotation.typeName.right.name}`
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
function matchesProtobufPackage(packageName, protobufPackages) {
|
||||
return protobufPackages.some((protobufPackage) => {
|
||||
// Remove leading and trailing @ and / from protobufPackage
|
||||
const cleanedPackage = protobufPackage.replace(/^[@\/]/, "").replace(/[\/]$/, "")
|
||||
const pattern = new RegExp(`(.*[@/]|)${escapeRegex(cleanedPackage)}[/].*`)
|
||||
return pattern.test(packageName)
|
||||
})
|
||||
}
|
||||
|
||||
// Helper function to escape special regex characters
|
||||
function escapeRegex(string) {
|
||||
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
||||
}
|
||||
|
||||
// Helper to extract function return type more reliably
|
||||
function getFunctionReturnType(functionNode, sourceCode) {
|
||||
// 1. Check explicit return type annotation
|
||||
if (functionNode.returnType) {
|
||||
return getTypeName(functionNode.returnType.typeAnnotation)
|
||||
}
|
||||
|
||||
// 2. For variable declarations like const foo: (arg: Type) => ReturnType = ...
|
||||
if (functionNode.parent && functionNode.parent.type === "VariableDeclarator") {
|
||||
const declarator = functionNode.parent
|
||||
if (declarator.id && declarator.id.typeAnnotation) {
|
||||
const typeAnnotation = declarator.id.typeAnnotation.typeAnnotation
|
||||
|
||||
// Handle function type annotations
|
||||
if (typeAnnotation.type === "TSFunctionType" && typeAnnotation.typeAnnotation) {
|
||||
return getTypeName(typeAnnotation.typeAnnotation)
|
||||
}
|
||||
|
||||
// Handle type references to function types
|
||||
if (typeAnnotation.type === "TSTypeReference") {
|
||||
// This might be a type like Promise<ReturnType>
|
||||
if (
|
||||
typeAnnotation.typeName.name === "Promise" &&
|
||||
typeAnnotation.typeParameters &&
|
||||
typeAnnotation.typeParameters.params.length > 0
|
||||
) {
|
||||
return getTypeName(typeAnnotation.typeParameters.params[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. For class methods, check if it's part of an interface implementation
|
||||
if (
|
||||
functionNode.parent &&
|
||||
functionNode.parent.type === "MethodDefinition" &&
|
||||
functionNode.parent.parent &&
|
||||
functionNode.parent.parent.type === "ClassBody"
|
||||
) {
|
||||
const className = getEnclosingClassName(functionNode)
|
||||
const methodName = functionNode.parent.key.name
|
||||
|
||||
if (className && methodName) {
|
||||
// Look for interface declarations in the scope
|
||||
const scope = sourceCode.getScope(functionNode)
|
||||
// This would require more complex scope analysis which is limited in ESLint
|
||||
// For now, we'll return null and rely on other methods
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
// Helper to get the class name for a method
|
||||
function getEnclosingClassName(node) {
|
||||
let current = node.parent
|
||||
while (current) {
|
||||
if (current.type === "ClassDeclaration" && current.id) {
|
||||
return current.id.name
|
||||
}
|
||||
current = current.parent
|
||||
}
|
||||
return null
|
||||
}
|
||||
function isNamespacedProtobufType(protobufNamespaceImports, typeName) {
|
||||
if (!typeName.includes(".")) {
|
||||
return false
|
||||
}
|
||||
|
||||
const namespace = typeName.split(".")[0]
|
||||
return protobufNamespaceImports.has(namespace)
|
||||
}
|
||||
|
||||
function findParentFunction(node) {
|
||||
let current = node.parent
|
||||
while (current) {
|
||||
if (
|
||||
current.type === "FunctionDeclaration" ||
|
||||
current.type === "FunctionExpression" ||
|
||||
current.type === "ArrowFunctionExpression"
|
||||
) {
|
||||
return current
|
||||
}
|
||||
current = current.parent
|
||||
}
|
||||
return null
|
||||
}
|
||||
Generated
-2479
File diff suppressed because it is too large
Load Diff
@@ -1,31 +0,0 @@
|
||||
{
|
||||
"name": "eslint-plugin-eslint-rules",
|
||||
"version": "1.0.0",
|
||||
"description": "Custom ESLint rules for Cline",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "mocha --no-config --require ts-node/register __tests__/**/*.test.ts"
|
||||
},
|
||||
"keywords": [
|
||||
"eslint",
|
||||
"eslintplugin"
|
||||
],
|
||||
"author": "Cline Bot Inc.",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/utils": "^8.33.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/eslint": "^8.0.0",
|
||||
"@types/mocha": "^10.0.7",
|
||||
"@types/node": "^20.0.0",
|
||||
"@typescript-eslint/parser": "^7.14.1",
|
||||
"eslint": "^8.57.0",
|
||||
"mocha": "^10.0.0",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.4.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": ">=8.0.0"
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2020",
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"outDir": "dist",
|
||||
"resolveJsonModule": true,
|
||||
"declaration": true
|
||||
},
|
||||
"include": ["**/*.ts", "**/*.js", "**/*.tsx", "__tests__/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
Generated
+12351
-24515
File diff suppressed because it is too large
Load Diff
+15
-12
@@ -331,19 +331,20 @@
|
||||
"watch:tsc": "tsc --noEmit --watch --project tsconfig.json",
|
||||
"package": "npm run check-types && npm run build:webview && npm run lint && node esbuild.js --production",
|
||||
"protos": "node scripts/build-proto.mjs && node scripts/generate-server-setup.mjs && node scripts/generate-host-bridge-client.mjs",
|
||||
"postprotos": "prettier src/shared/proto src/core/controller src/hosts/ webview-ui/src/services src/generated --write --log-level warn",
|
||||
"postprotos": "biome check src/shared/proto src/core/controller src/hosts/ webview-ui/src/services src/generated --write --log-level warn",
|
||||
"clean": "rimraf dist dist-standalone webview-ui/build src/generated out/",
|
||||
"compile-tests": "node ./scripts/build-tests.js",
|
||||
"watch-tests": "tsc -p . -w --outDir out",
|
||||
"check-types": "npm run protos && npx tsc --noEmit && cd webview-ui && npx tsc -b --noEmit",
|
||||
"lint": "eslint src --ext ts && eslint webview-ui/src --ext ts && buf lint && cd webview-ui && npm run lint",
|
||||
"format": "prettier . --check",
|
||||
"format:fix": "prettier . --write",
|
||||
"lint": "biome lint --staged",
|
||||
"format": "biome check --staged",
|
||||
"format:fix": "biome format --write --staged",
|
||||
"pretest": "npm run compile && npm run compile-tests && npm run compile-standalone && npm run lint",
|
||||
"test": "npm-run-all test:unit test:integration",
|
||||
"test": "npm-run-all test:unit test:integration test:vscode-deprecation",
|
||||
"test:ci": "node scripts/test-ci.js",
|
||||
"test:integration": "vscode-test",
|
||||
"test:unit": "TS_NODE_PROJECT='./tsconfig.unit-test.json' mocha",
|
||||
"test:vscode-deprecation": "node src/hosts/vscode/__tests__/vscode-deprecation-test.js",
|
||||
"test:coverage": "vscode-test --coverage",
|
||||
"e2e": "playwright test -c playwright.config.ts",
|
||||
"test:e2e": "playwright install && vsce package --no-dependencies --out dist/e2e.vsix && node src/test/e2e/utils/build.js && playwright test",
|
||||
@@ -363,7 +364,7 @@
|
||||
},
|
||||
"lint-staged": {
|
||||
"*": [
|
||||
"prettier --write --ignore-unknown --log-level=log"
|
||||
"biome check --no-errors-on-unmatched --files-ignore-unknown=true"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -381,17 +382,12 @@
|
||||
"@types/sinon": "^17.0.4",
|
||||
"@types/turndown": "^5.0.5",
|
||||
"@types/vscode": "^1.84.0",
|
||||
"@typescript-eslint/eslint-plugin": "^7.14.1",
|
||||
"@typescript-eslint/parser": "^7.18.0",
|
||||
"@typescript-eslint/utils": "^8.33.0",
|
||||
"@vscode/test-cli": "^0.0.10",
|
||||
"@vscode/test-electron": "^2.5.2",
|
||||
"@vscode/vsce": "^3.6.0",
|
||||
"chai": "^4.3.10",
|
||||
"chalk": "^5.3.0",
|
||||
"esbuild": "^0.25.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-eslint-rules": "file:eslint-rules",
|
||||
"grpc-tools": "^1.13.0",
|
||||
"husky": "^9.1.7",
|
||||
"lint-staged": "^16.1.0",
|
||||
@@ -414,6 +410,7 @@
|
||||
"@anthropic-ai/vertex-sdk": "^0.6.4",
|
||||
"@aws-sdk/client-bedrock-runtime": "^3.840.0",
|
||||
"@aws-sdk/credential-providers": "^3.840.0",
|
||||
"@biomejs/biome": "^2.1.2",
|
||||
"@bufbuild/protobuf": "^2.2.5",
|
||||
"@cerebras/cerebras_cloud_sdk": "^1.35.0",
|
||||
"@google-cloud/vertexai": "^1.9.3",
|
||||
@@ -478,5 +475,11 @@
|
||||
"vscode-uri": "^3.1.0",
|
||||
"web-tree-sitter": "^0.22.6",
|
||||
"zod": "^3.24.2"
|
||||
}
|
||||
},
|
||||
"workspaces": [
|
||||
"docs",
|
||||
"evals",
|
||||
"standalone",
|
||||
"webview-ui"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Test script to verify VSCode API deprecation warnings are properly configured.
|
||||
* This script validates that the TypeScript declaration overrides in vscode.d.ts
|
||||
* contain the expected deprecation markers and alternative recommendations.
|
||||
*/
|
||||
|
||||
const fs = require("node:fs")
|
||||
const path = require("node:path")
|
||||
const { execSync } = require("node:child_process")
|
||||
|
||||
function runVscodeDeprecationTests() {
|
||||
console.log("🔍 Testing VSCode API Deprecation Warnings...")
|
||||
|
||||
const declarationPath = path.join(__dirname, "..", "vscode.d.ts")
|
||||
|
||||
// Test 1: Check declaration file exists
|
||||
if (!fs.existsSync(declarationPath)) {
|
||||
console.error("❌ VSCode declaration file not found at:", declarationPath)
|
||||
process.exit(1)
|
||||
}
|
||||
console.log("✅ Declaration file exists")
|
||||
|
||||
const content = fs.readFileSync(declarationPath, "utf8")
|
||||
|
||||
// Test 2: Check for deprecation markers
|
||||
if (!content.includes("@deprecated")) {
|
||||
console.error("❌ Declaration file missing @deprecated annotations")
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
if (!content.includes("@internal")) {
|
||||
console.error("❌ Declaration file missing @internal annotations")
|
||||
process.exit(1)
|
||||
}
|
||||
console.log("✅ Declaration file has proper deprecation markers")
|
||||
|
||||
// Test 3: Check for specific deprecated APIs
|
||||
const deprecatedApis = [
|
||||
"workspaceFolders",
|
||||
"writeFile",
|
||||
"postMessage",
|
||||
"showTextDocument",
|
||||
"showOpenDialog",
|
||||
"stat",
|
||||
"asRelativePath",
|
||||
"getWorkspaceFolder",
|
||||
"applyEdit",
|
||||
]
|
||||
|
||||
for (const api of deprecatedApis) {
|
||||
if (!content.includes(api)) {
|
||||
console.error(`❌ Missing deprecated API: ${api}`)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
console.log("✅ All expected deprecated APIs found")
|
||||
|
||||
// Test 4: Check for alternative recommendations
|
||||
const expectedAlternatives = ["gRPC service clients", "@/utils/fs", "getHostBridgeProvider", "@/utils/path", "host bridge"]
|
||||
|
||||
for (const alternative of expectedAlternatives) {
|
||||
if (!content.includes(alternative)) {
|
||||
console.error(`❌ Missing alternative recommendation: ${alternative}`)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
console.log("✅ Alternative recommendations found")
|
||||
|
||||
// Test 5: Verify TypeScript compilation with deprecated API usage
|
||||
const tempTestContent = `
|
||||
import * as vscode from "vscode"
|
||||
|
||||
// This should trigger deprecation warnings but not fail compilation
|
||||
const folders = vscode.workspace.workspaceFolders
|
||||
vscode.workspace.fs.writeFile(vscode.Uri.file("test"), new Uint8Array())
|
||||
vscode.window.showTextDocument(vscode.Uri.file("test"))
|
||||
`
|
||||
|
||||
const tempTestFile = path.join(__dirname, "temp-deprecation-test.ts")
|
||||
|
||||
try {
|
||||
fs.writeFileSync(tempTestFile, tempTestContent)
|
||||
|
||||
// Run TypeScript compiler to check for compilation errors
|
||||
// We use --noEmit to just check types without generating output
|
||||
execSync(`npx tsc --noEmit --strict --skipLibCheck "${tempTestFile}"`, {
|
||||
cwd: path.join(__dirname, "..", "..", ".."),
|
||||
stdio: "pipe", // Suppress output unless there's an error
|
||||
})
|
||||
|
||||
console.log("✅ TypeScript compilation succeeds with deprecated API usage")
|
||||
} catch (error) {
|
||||
// Check if it's a compilation error vs deprecation warnings
|
||||
const output = error.stdout?.toString() || error.stderr?.toString() || ""
|
||||
|
||||
// If there are actual TypeScript errors (not just deprecation warnings), fail the test
|
||||
if (output.includes("error TS") && !output.includes("deprecated")) {
|
||||
console.error("❌ Unexpected TypeScript compilation errors:")
|
||||
console.error(output)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// If it's just deprecation warnings or no errors, that's expected
|
||||
console.log("✅ TypeScript compilation handles deprecated API usage correctly")
|
||||
} finally {
|
||||
// Clean up the temporary test file
|
||||
try {
|
||||
fs.unlinkSync(tempTestFile)
|
||||
} catch {
|
||||
// Ignore cleanup errors
|
||||
}
|
||||
}
|
||||
|
||||
console.log("🎉 All VSCode API deprecation tests passed!")
|
||||
return true
|
||||
}
|
||||
|
||||
// Run the tests if this script is executed directly
|
||||
if (require.main === module) {
|
||||
runVscodeDeprecationTests()
|
||||
}
|
||||
|
||||
module.exports = { runVscodeDeprecationTests }
|
||||
@@ -0,0 +1,12 @@
|
||||
const { expect } = require("chai")
|
||||
const { describe, it } = require("mocha")
|
||||
const { runVscodeDeprecationTests } = require("./vscode-deprecation-test")
|
||||
|
||||
describe("VSCode API Deprecation Warnings", () => {
|
||||
it("should have proper TypeScript declaration overrides with deprecation warnings", () => {
|
||||
// This test wraps our standalone test script
|
||||
// The script will throw/exit if any test fails, so if we get here, all tests passed
|
||||
const result = runVscodeDeprecationTests()
|
||||
expect(result).to.be.true
|
||||
})
|
||||
})
|
||||
Vendored
+108
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* TypeScript declaration overrides to show deprecation warnings for direct VSCode API usage.
|
||||
* These APIs should be accessed through Cline's abstraction layers instead.
|
||||
*
|
||||
* This approach uses TypeScript's module augmentation to add deprecation warnings
|
||||
* and make the APIs less discoverable in IntelliSense by marking them as @internal.
|
||||
*
|
||||
* Exemptions: Files in src/hosts/vscode/ and standalone/runtime-files/ directories
|
||||
* are allowed to use these APIs directly as they provide the abstraction layer.
|
||||
*/
|
||||
|
||||
declare module "vscode" {
|
||||
export interface ExtensionContext {
|
||||
/**
|
||||
* @deprecated Use gRPC service clients instead of vscode.postMessage().
|
||||
* Example: AccountServiceClient.methodName(RequestType.create({...})) instead of vscode.postMessage({type: '...'}).
|
||||
* This provides better type safety and consistent communication patterns.
|
||||
* @internal
|
||||
*/
|
||||
postMessage?: (message: any) => Thenable<boolean>
|
||||
}
|
||||
|
||||
export namespace workspace {
|
||||
export namespace fs {
|
||||
/**
|
||||
* @deprecated Use utilities in @/utils/fs instead of vscode.workspace.fs.stat.
|
||||
* Example: import { isDirectory } from '@/utils/fs' or use the file system methods from the host bridge provider.
|
||||
* This provides consistent file system access across VSCode and standalone environments.
|
||||
* @internal
|
||||
*/
|
||||
export function stat(uri: Uri): Thenable<FileStat>
|
||||
|
||||
/**
|
||||
* @deprecated Use utilities in @/utils/fs instead of vscode.workspace.fs.writeFile.
|
||||
* Example: import { writeFile } from '@/utils/fs' or use the file system methods from the host bridge provider.
|
||||
* This provides consistent file system access across VSCode and standalone environments.
|
||||
* @internal
|
||||
*/
|
||||
export function writeFile(uri: Uri, content: Uint8Array): Thenable<void>
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use getHostBridgeProvider().workspaceClient.getWorkspacePaths({}) instead of vscode.workspace.workspaceFolders.
|
||||
* This provides a consistent abstraction across VSCode and standalone environments.
|
||||
* @internal
|
||||
*/
|
||||
export const workspaceFolders: readonly WorkspaceFolder[] | undefined
|
||||
|
||||
/**
|
||||
* @deprecated Use path utilities from @/utils/path instead of vscode.workspace.asRelativePath.
|
||||
* This provides consistent path handling across different environments.
|
||||
* @internal
|
||||
*/
|
||||
export function asRelativePath(pathOrUri: string | Uri, includeWorkspaceFolder?: boolean): string
|
||||
|
||||
/**
|
||||
* @deprecated Use path utilities from @/utils/path instead of vscode.workspace.getWorkspaceFolder.
|
||||
* This provides consistent path handling across different environments.
|
||||
* @internal
|
||||
*/
|
||||
export function getWorkspaceFolder(uri: Uri): WorkspaceFolder | undefined
|
||||
|
||||
/**
|
||||
* @deprecated Use the host bridge instead of vscode.workspace.applyEdit.
|
||||
* This provides a consistent abstraction across VSCode and standalone environments.
|
||||
* @internal
|
||||
*/
|
||||
export function applyEdit(edit: WorkspaceEdit): Thenable<boolean>
|
||||
}
|
||||
|
||||
export namespace window {
|
||||
/**
|
||||
* @deprecated Use the host bridge instead of vscode.window.showTextDocument.
|
||||
* This provides a consistent abstraction across VSCode and standalone environments.
|
||||
* @internal
|
||||
*/
|
||||
export function showTextDocument(
|
||||
document: TextDocument | Uri,
|
||||
column?: ViewColumn,
|
||||
preserveFocus?: boolean,
|
||||
): Thenable<TextEditor>
|
||||
|
||||
/**
|
||||
* @deprecated Use getHostBridgeProvider().windowClient.showMessage instead of vscode.window.showOpenDialog.
|
||||
* This provides a consistent abstraction across VSCode and standalone environments.
|
||||
* @internal
|
||||
*/
|
||||
export function showOpenDialog(options?: OpenDialogOptions): Thenable<Uri[] | undefined>
|
||||
}
|
||||
|
||||
export interface Webview {
|
||||
/**
|
||||
* @deprecated Use gRPC service clients instead of webview.postMessage().
|
||||
* Example: AccountServiceClient.methodName(RequestType.create({...})) instead of webview.postMessage({type: '...'}).
|
||||
* This provides better type safety and consistent communication patterns.
|
||||
* @internal
|
||||
*/
|
||||
postMessage(message: any): Thenable<boolean>
|
||||
}
|
||||
|
||||
export interface WebviewPanel {
|
||||
/**
|
||||
* Access to the webview belonging to this panel.
|
||||
* Note: webview.postMessage is deprecated - use gRPC service clients instead.
|
||||
*/
|
||||
readonly webview: Webview
|
||||
}
|
||||
}
|
||||
+3
-2
@@ -4,7 +4,7 @@
|
||||
"experimentalDecorators": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"isolatedModules": true,
|
||||
"lib": ["es2022", "esnext.disposable", "DOM"],
|
||||
"lib": ["es2022", "DOM"],
|
||||
"module": "esnext",
|
||||
"moduleResolution": "Bundler",
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
@@ -31,7 +31,8 @@
|
||||
"@services/*": ["src/services/*"],
|
||||
"@shared/*": ["src/shared/*"],
|
||||
"@utils/*": ["src/utils/*"]
|
||||
}
|
||||
},
|
||||
"types": ["nlcst"]
|
||||
},
|
||||
"include": ["src/**/*", "scripts/**/*"],
|
||||
"exclude": ["node_modules", ".vscode-test", "webview-ui", "src/test/e2e/**/*"]
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
{
|
||||
"root": true,
|
||||
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2020,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": ["@typescript-eslint", "react-hooks", "react-refresh", "eslint-rules"],
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2020": true
|
||||
},
|
||||
"rules": {
|
||||
"react-hooks/rules-of-hooks": "error",
|
||||
// "react-refresh/only-export-components": [
|
||||
// "warn",
|
||||
// {
|
||||
// "allowConstantExport": true
|
||||
// }
|
||||
// ],
|
||||
"@typescript-eslint/no-unused-vars": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-empty-object-type": "off",
|
||||
"no-case-declarations": "off",
|
||||
"react-hooks/exhaustive-deps": "off",
|
||||
"prefer-const": "off",
|
||||
"no-extra-semi": "off",
|
||||
"eslint-rules/no-protobuf-object-literals": "error",
|
||||
"eslint-rules/no-grpc-client-object-literals": "error",
|
||||
"eslint-rules/no-direct-vscode-api": "warn",
|
||||
"no-restricted-syntax": [
|
||||
"error",
|
||||
{
|
||||
"selector": "VariableDeclarator[id.type=\"ObjectPattern\"][init.object.name=\"process\"][init.property.name=\"env\"]",
|
||||
"message": "Use process.env.VARIABLE_NAME directly instead of destructuring"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ignorePatterns": ["build"]
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
{
|
||||
"root": false,
|
||||
"vcs": {
|
||||
"enabled": true,
|
||||
"clientKind": "git",
|
||||
"useIgnoreFile": true,
|
||||
"defaultBranch": "main"
|
||||
},
|
||||
"assist": {
|
||||
"actions": {
|
||||
"source": {
|
||||
"organizeImports": "off"
|
||||
}
|
||||
}
|
||||
},
|
||||
"linter": {
|
||||
"enabled": true,
|
||||
"rules": {
|
||||
"recommended": true,
|
||||
"a11y": "off",
|
||||
"correctness": {
|
||||
"noUnusedVariables": "off",
|
||||
"useExhaustiveDependencies": "off",
|
||||
"noUnusedImports": "off",
|
||||
"noSwitchDeclarations": "off",
|
||||
"noUndeclaredVariables": "off",
|
||||
"useJsxKeyInIterable": "off",
|
||||
"noUnusedFunctionParameters": "off",
|
||||
"noInvalidPositionAtImportRule": "off",
|
||||
"useHookAtTopLevel": "off"
|
||||
},
|
||||
"style": {
|
||||
"useConst": "off",
|
||||
"noNonNullAssertion": "off",
|
||||
"useNodejsImportProtocol": "off",
|
||||
"useImportType": "off",
|
||||
"useBlockStatements": "off",
|
||||
"useExponentiationOperator": "off",
|
||||
"useTemplate": "off",
|
||||
"useNamingConvention": {
|
||||
"level": "off",
|
||||
"options": {
|
||||
"strictCase": false,
|
||||
"requireAscii": false,
|
||||
"conventions": [
|
||||
{
|
||||
"selector": { "kind": "importNamespace" },
|
||||
"formats": ["camelCase", "PascalCase"]
|
||||
},
|
||||
{
|
||||
"selector": { "kind": "importAlias" },
|
||||
"formats": ["camelCase", "PascalCase"]
|
||||
},
|
||||
{
|
||||
"selector": { "kind": "function" },
|
||||
"formats": ["camelCase", "PascalCase"]
|
||||
},
|
||||
{
|
||||
"selector": { "kind": "variable" },
|
||||
"formats": ["camelCase", "PascalCase", "CONSTANT_CASE"]
|
||||
},
|
||||
{
|
||||
"selector": { "kind": "objectLiteralMember" },
|
||||
"formats": ["camelCase", "PascalCase", "CONSTANT_CASE"]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"suspicious": {
|
||||
"noExplicitAny": "off",
|
||||
"noDoubleEquals": "off",
|
||||
"noShadowRestrictedNames": "off",
|
||||
"noArrayIndexKey": "off",
|
||||
"noGlobalIsNan": "off",
|
||||
"noConfusingVoidType": "off",
|
||||
"noAssignInExpressions": "off"
|
||||
},
|
||||
"complexity": {
|
||||
"noBannedTypes": "off",
|
||||
"useOptionalChain": "off",
|
||||
"useLiteralKeys": "off",
|
||||
"noUselessConstructor": "off",
|
||||
"noUselessSwitchCase": "off",
|
||||
"noUselessFragments": "off"
|
||||
},
|
||||
"security": {
|
||||
"noDangerouslySetInnerHtml": "off"
|
||||
}
|
||||
}
|
||||
},
|
||||
"formatter": {
|
||||
"enabled": true,
|
||||
"indentStyle": "tab",
|
||||
"indentWidth": 4,
|
||||
"lineWidth": 130,
|
||||
"lineEnding": "lf"
|
||||
},
|
||||
"javascript": {
|
||||
"formatter": {
|
||||
"semicolons": "asNeeded",
|
||||
"arrowParentheses": "always",
|
||||
"bracketSameLine": true,
|
||||
"bracketSpacing": true,
|
||||
"jsxQuoteStyle": "double",
|
||||
"quoteProperties": "asNeeded",
|
||||
"trailingCommas": "all"
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
-1
@@ -53,7 +53,6 @@
|
||||
"@vitejs/plugin-react-swc": "^3.5.0",
|
||||
"@vitest/coverage-v8": "^3.0.9",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-eslint-rules": "file:../eslint-rules",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.16",
|
||||
"globals": "^15.14.0",
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
"build": "tsc -b && vite build",
|
||||
"build:test": "tsc -b && vite build",
|
||||
"preview": "vite preview",
|
||||
"lint": "eslint . --ext .ts,.tsx",
|
||||
"lint": "biome check --staged",
|
||||
"fix": "biome check --write",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest dev",
|
||||
"test:coverage": "vitest run --coverage",
|
||||
@@ -45,7 +46,6 @@
|
||||
"uuid": "^9.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.17.0",
|
||||
"@tailwindcss/vite": "^4.1.4",
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
"@testing-library/react": "^16.2.0",
|
||||
@@ -59,16 +59,11 @@
|
||||
"@types/vscode-webview": "^1.57.5",
|
||||
"@vitejs/plugin-react-swc": "^3.5.0",
|
||||
"@vitest/coverage-v8": "^3.0.9",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-eslint-rules": "file:../eslint-rules",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.16",
|
||||
"globals": "^15.14.0",
|
||||
"jsdom": "^26.0.0",
|
||||
"react-devtools": "^6.1.2",
|
||||
"tailwindcss": "^4.1.5",
|
||||
"typescript": "^5.7.3",
|
||||
"typescript-eslint": "^8.18.2",
|
||||
"vite": "^6.3.4",
|
||||
"vitest": "^3.0.5"
|
||||
},
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
"strict": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
|
||||
/* Aliasing */
|
||||
"baseUrl": ".",
|
||||
|
||||
Reference in New Issue
Block a user