Merge branch 'master' into list-extensions

This commit is contained in:
Sandeep Somavarapu 2021-01-08 12:01:16 +01:00 committed by GitHub
commit 7b16f15d00
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
584 changed files with 17543 additions and 11068 deletions

File diff suppressed because it is too large Load diff

View file

@ -11,25 +11,33 @@ on:
- release/*
jobs:
hygiene:
name: Hygiene and Layering
runs-on: ubuntu-latest
windows:
name: Windows
runs-on: windows-latest
env:
CHILD_CONCURRENCY: "1"
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2-beta
- uses: actions/setup-node@v2
with:
node-version: 12
- uses: actions/setup-python@v2
with:
python-version: "2.x"
- name: Compute node modules cache key
id: nodeModulesCacheKey
run: echo "::set-output name=value::$(node build/azure-pipelines/common/computeNodeModulesCacheKey.js)"
- name: Cache node modules
id: cacheNodeModules
uses: actions/cache@v2
with:
path: "**/node_modules"
key: ${{ runner.os }}-cacheNodeModules6-${{ hashFiles('.yarnrc', 'remote/.yarnrc', '**/yarn.lock', '!**/node_modules/**/yarn.lock', '!**/.*/**/yarn.lock') }}
restore-keys: ${{ runner.os }}-cacheNodeModules6-
key: ${{ runner.os }}-cacheNodeModules8-${{ steps.nodeModulesCacheKey.outputs.value }}
restore-keys: ${{ runner.os }}-cacheNodeModules8-
- name: Get yarn cache directory path
id: yarnCacheDirPath
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
@ -39,7 +47,7 @@ jobs:
uses: actions/cache@v2
with:
path: ${{ steps.yarnCacheDirPath.outputs.dir }}
key: ${{ runner.os }}-yarnCacheDir-${{ hashFiles('.yarnrc', 'remote/.yarnrc', '**/yarn.lock', '!**/node_modules/**/yarn.lock', '!**/.*/**/yarn.lock') }}
key: ${{ runner.os }}-yarnCacheDir-${{ steps.nodeModulesCacheKey.outputs.value }}
restore-keys: ${{ runner.os }}-yarnCacheDir-
- name: Execute yarn
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
@ -48,414 +56,17 @@ jobs:
ELECTRON_SKIP_BINARY_DOWNLOAD: 1
run: yarn --frozen-lockfile --network-timeout 180000
- name: Run Hygiene Checks
run: yarn gulp hygiene
- name: Compile and Download
run: yarn npm-run-all --max_old_space_size=4095 -lp compile "electron x64" playwright-install download-builtin-extensions
- name: Run Valid Layers Checks
run: yarn valid-layers-check
- name: Run Unit Tests (Electron)
run: .\scripts\test.bat
# build-compile-core:
# name: "Build: Compile Core"
# runs-on: ubuntu-latest
# env:
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# steps:
# - uses: actions/checkout@v2
- name: Run Unit Tests (Browser)
run: yarn test-browser --browser chromium
# - uses: actions/setup-node@v2-beta
# with:
# node-version: 12
# - name: Cache node modules
# id: cacheNodeModules
# uses: actions/cache@v2
# with:
# path: '**/node_modules'
# key: ${{ runner.os }}-cacheNodeModules6-${{ hashFiles('.yarnrc', 'remote/.yarnrc', '**/yarn.lock', '!**/node_modules/**/yarn.lock', '!**/.*/**/yarn.lock') }}
# restore-keys: ${{ runner.os }}-cacheNodeModules6-
# - name: Get yarn cache directory path
# id: yarnCacheDirPath
# if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
# run: echo "::set-output name=dir::$(yarn cache dir)"
# - name: Cache yarn directory
# if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
# uses: actions/cache@v2
# with:
# path: ${{ steps.yarnCacheDirPath.outputs.dir }}
# key: ${{ runner.os }}-yarnCacheDir-${{ hashFiles('.yarnrc', 'remote/.yarnrc', '**/yarn.lock', '!**/node_modules/**/yarn.lock', '!**/.*/**/yarn.lock') }}
# restore-keys: ${{ runner.os }}-yarnCacheDir-
# - name: Execute yarn
# if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
# env:
# PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
# ELECTRON_SKIP_BINARY_DOWNLOAD: 1
# run: yarn --frozen-lockfile --network-timeout 180000
# - name: Cache compiled core code
# id: cacheCompiledCoreCode
# uses: actions/cache@v2
# with:
# path: |
# out-build
# out-vscode-min
# key: cacheCompiledCoreCode-${{ github.sha }}
# - name: Compile Core
# if: ${{ steps.cacheCompiledCoreCode.outputs.cache-hit != 'true' }}
# run: yarn gulp compile-build
# - name: Minify VS Code
# if: ${{ steps.cacheCompiledCoreCode.outputs.cache-hit != 'true' }}
# run: yarn gulp minify-vscode
# build-compile-extensions:
# name: "Build: Compile Extensions"
# runs-on: ubuntu-latest
# env:
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# steps:
# - uses: actions/checkout@v2
# - uses: actions/setup-node@v2-beta
# with:
# node-version: 12
# - name: Cache node modules
# id: cacheNodeModules
# uses: actions/cache@v2
# with:
# path: '**/node_modules'
# key: ${{ runner.os }}-cacheNodeModules6-${{ hashFiles('.yarnrc', 'remote/.yarnrc', '**/yarn.lock', '!**/node_modules/**/yarn.lock', '!**/.*/**/yarn.lock') }}
# restore-keys: ${{ runner.os }}-cacheNodeModules6-
# - name: Get yarn cache directory path
# id: yarnCacheDirPath
# if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
# run: echo "::set-output name=dir::$(yarn cache dir)"
# - name: Cache yarn directory
# if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
# uses: actions/cache@v2
# with:
# path: ${{ steps.yarnCacheDirPath.outputs.dir }}
# key: ${{ runner.os }}-yarnCacheDir-${{ hashFiles('.yarnrc', 'remote/.yarnrc', '**/yarn.lock', '!**/node_modules/**/yarn.lock', '!**/.*/**/yarn.lock') }}
# restore-keys: ${{ runner.os }}-yarnCacheDir-
# - name: Execute yarn
# if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
# env:
# PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
# ELECTRON_SKIP_BINARY_DOWNLOAD: 1
# run: yarn --frozen-lockfile --network-timeout 180000
# - name: Cache compiled extensions code
# id: cacheCompiledExtensionsCode
# uses: actions/cache@v2
# with:
# path: .build
# key: cacheCompiledExtensionsCode-${{ github.sha }}
# - name: Compile Extensions
# if: ${{ steps.cacheCompiledExtensionsCode.outputs.cache-hit != 'true' }}
# run: yarn gulp compile-extensions-build
# build-linux-unit-tests:
# name: "Build: Linux Unit Tests"
# needs: [build-compile-core, build-compile-extensions]
# runs-on: ubuntu-latest
# env:
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# steps:
# - uses: actions/checkout@v2
# # TODO: rename azure-pipelines/linux/xvfb.init to github-actions
# - name: Setup Build Environment
# run: |
# sudo apt-get update
# sudo apt-get install -y libxkbfile-dev pkg-config libsecret-1-dev libxss1 dbus xvfb libgtk-3-0 libgbm1
# sudo cp build/azure-pipelines/linux/xvfb.init /etc/init.d/xvfb
# sudo chmod +x /etc/init.d/xvfb
# sudo update-rc.d xvfb defaults
# sudo service xvfb start
# - uses: actions/setup-node@v2-beta
# with:
# node-version: 12
# - name: Restore cached node modules
# id: cacheNodeModules
# uses: actions/cache@v2
# with:
# path: '**/node_modules'
# key: ${{ runner.os }}-cacheNodeModules6-${{ hashFiles('.yarnrc', 'remote/.yarnrc', '**/yarn.lock', '!**/node_modules/**/yarn.lock', '!**/.*/**/yarn.lock') }}
# restore-keys: ${{ runner.os }}-cacheNodeModules6-
# - name: Restore compiled core code
# id: cacheCompiledCoreCode
# uses: actions/cache@v2
# with:
# path: |
# out-build
# out-vscode-min
# key: cacheCompiledCoreCode-${{ github.sha }}
# - name: Restore compiled extensions code
# id: cacheCompiledExtensionsCode
# uses: actions/cache@v2
# with:
# path: .build
# key: cacheCompiledExtensionsCode-${{ github.sha }}
# - name: Build VS Code
# run: yarn gulp vscode-linux-x64-min-ci
# - name: Download Electron
# run: yarn electron x64
# - name: Run Unit Tests (Electron)
# run: DISPLAY=:10 ./scripts/test.sh --build
# - name: Download Playwright
# run: node ./node_modules/playwright/install.js
# - name: Run Unit Tests (Browser)
# run: DISPLAY=:10 yarn test-browser --build --browser chromium
# build-linux-integration-tests:
# name: "Build: Linux Integration Tests"
# needs: [build-compile-core, build-compile-extensions]
# runs-on: ubuntu-latest
# env:
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# steps:
# - uses: actions/checkout@v2
# # TODO: rename azure-pipelines/linux/xvfb.init to github-actions
# - name: Setup Build Environment
# run: |
# sudo apt-get update
# sudo apt-get install -y libxkbfile-dev pkg-config libsecret-1-dev libxss1 dbus xvfb libgtk-3-0 libgbm1
# sudo cp build/azure-pipelines/linux/xvfb.init /etc/init.d/xvfb
# sudo chmod +x /etc/init.d/xvfb
# sudo update-rc.d xvfb defaults
# sudo service xvfb start
# - uses: actions/setup-node@v2-beta
# with:
# node-version: 12
# - name: Restore cached node modules
# id: cacheNodeModules
# uses: actions/cache@v2
# with:
# path: '**/node_modules'
# key: ${{ runner.os }}-cacheNodeModules6-${{ hashFiles('.yarnrc', 'remote/.yarnrc', '**/yarn.lock', '!**/node_modules/**/yarn.lock', '!**/.*/**/yarn.lock') }}
# restore-keys: ${{ runner.os }}-cacheNodeModules6-
# - name: Restore compiled core code
# id: cacheCompiledCoreCode
# uses: actions/cache@v2
# with:
# path: |
# out-build
# out-vscode-min
# key: cacheCompiledCoreCode-${{ github.sha }}
# - name: Restore compiled extensions code
# id: cacheCompiledExtensionsCode
# uses: actions/cache@v2
# with:
# path: .build
# key: cacheCompiledExtensionsCode-${{ github.sha }}
# - name: Build VS Code
# run: yarn gulp vscode-linux-x64-min-ci
# - name: Download Electron
# run: yarn electron x64
# - name: Run Integration Tests (Electron)
# run: DISPLAY=:10 ./scripts/test-integration.sh --build
# build-darwin-node-modules:
# name: "Build: macOS Node Modules"
# runs-on: macos-latest
# env:
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# steps:
# - uses: actions/checkout@v2
# - uses: actions/setup-node@v2-beta
# with:
# node-version: 12
# - name: Cache node modules
# id: cacheNodeModules
# uses: actions/cache@v2
# with:
# path: '**/node_modules'
# key: ${{ runner.os }}-cacheNodeModules6-${{ hashFiles('.yarnrc', 'remote/.yarnrc', '**/yarn.lock', '!**/node_modules/**/yarn.lock', '!**/.*/**/yarn.lock') }}
# restore-keys: ${{ runner.os }}-cacheNodeModules6-
# - name: Get yarn cache directory path
# id: yarnCacheDirPath
# if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
# run: echo "::set-output name=dir::$(yarn cache dir)"
# - name: Cache yarn directory
# if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
# uses: actions/cache@v2
# with:
# path: ${{ steps.yarnCacheDirPath.outputs.dir }}
# key: ${{ runner.os }}-yarnCacheDir-${{ hashFiles('.yarnrc', 'remote/.yarnrc', '**/yarn.lock', '!**/node_modules/**/yarn.lock', '!**/.*/**/yarn.lock') }}
# restore-keys: ${{ runner.os }}-yarnCacheDir-
# - name: Execute yarn
# if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
# env:
# PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
# ELECTRON_SKIP_BINARY_DOWNLOAD: 1
# run: yarn --frozen-lockfile --network-timeout 180000
# build-darwin-unit-tests:
# name: "Build: macOS Unit Tests"
# needs: [build-compile-core, build-compile-extensions, build-darwin-node-modules]
# runs-on: macos-latest
# env:
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# steps:
# - uses: actions/checkout@v2
# - uses: actions/setup-node@v2-beta
# with:
# node-version: 12
# - name: Restore cached node modules
# id: cacheNodeModules
# uses: actions/cache@v2
# with:
# path: '**/node_modules'
# key: ${{ runner.os }}-cacheNodeModules6-${{ hashFiles('.yarnrc', 'remote/.yarnrc', '**/yarn.lock', '!**/node_modules/**/yarn.lock', '!**/.*/**/yarn.lock') }}
# restore-keys: ${{ runner.os }}-cacheNodeModules6-
# - name: Restore compiled core code
# id: cacheCompiledCoreCode
# uses: actions/cache@v2
# with:
# path: |
# out-build
# out-vscode-min
# key: cacheCompiledCoreCode-${{ github.sha }}
# - name: Restore compiled extensions code
# id: cacheCompiledExtensionsCode
# uses: actions/cache@v2
# with:
# path: .build
# key: cacheCompiledExtensionsCode-${{ github.sha }}
# - name: Build VS Code
# run: yarn gulp vscode-darwin-x64-min-ci
# - name: Download Electron
# run: yarn electron x64
# - name: Run Unit Tests (Electron)
# run: ./scripts/test.sh --build
# - name: Download Playwright
# run: node ./node_modules/playwright/install.js
# - name: Run Unit Tests (Browser)
# run: yarn test-browser --build --browser chromium --browser webkit --browser firefox
# build-darwin-integration-tests:
# name: "Build: macOS Integration Tests"
# needs: [build-compile-core, build-compile-extensions, build-darwin-node-modules]
# runs-on: macos-latest
# env:
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# steps:
# - uses: actions/checkout@v2
# - uses: actions/setup-node@v2-beta
# with:
# node-version: 12
# - name: Restore cached node modules
# id: cacheNodeModules
# uses: actions/cache@v2
# with:
# path: '**/node_modules'
# key: ${{ runner.os }}-cacheNodeModules6-${{ hashFiles('.yarnrc', 'remote/.yarnrc', '**/yarn.lock', '!**/node_modules/**/yarn.lock', '!**/.*/**/yarn.lock') }}
# restore-keys: ${{ runner.os }}-cacheNodeModules6-
# - name: Restore compiled core code
# id: cacheCompiledCoreCode
# uses: actions/cache@v2
# with:
# path: |
# out-build
# out-vscode-min
# key: cacheCompiledCoreCode-${{ github.sha }}
# - name: Restore compiled extensions code
# id: cacheCompiledExtensionsCode
# uses: actions/cache@v2
# with:
# path: .build
# key: cacheCompiledExtensionsCode-${{ github.sha }}
# - name: Build VS Code
# run: yarn gulp vscode-darwin-x64-min-ci
# - name: Download Electron
# run: yarn electron x64
# - name: Run Integration Tests (Electron)
# run: ./scripts/test-integration.sh --build
# build-darwin-smoke-tests:
# name: "Build: macOS Smoke Tests"
# needs: [build-compile-core, build-compile-extensions, build-darwin-node-modules]
# runs-on: macos-latest
# env:
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# steps:
# - uses: actions/checkout@v2
# - uses: actions/setup-node@v2-beta
# with:
# node-version: 12
# - name: Restore cached node modules
# id: cacheNodeModules
# uses: actions/cache@v2
# with:
# path: '**/node_modules'
# key: ${{ runner.os }}-cacheNodeModules6-${{ hashFiles('.yarnrc', 'remote/.yarnrc', '**/yarn.lock', '!**/node_modules/**/yarn.lock', '!**/.*/**/yarn.lock') }}
# restore-keys: ${{ runner.os }}-cacheNodeModules6-
# - name: Restore compiled core code
# id: cacheCompiledCoreCode
# uses: actions/cache@v2
# with:
# path: |
# out-build
# out-vscode-min
# key: cacheCompiledCoreCode-${{ github.sha }}
# - name: Restore compiled extensions code
# id: cacheCompiledExtensionsCode
# uses: actions/cache@v2
# with:
# path: .build
# key: cacheCompiledExtensionsCode-${{ github.sha }}
# - name: Build VS Code
# run: yarn gulp vscode-darwin-x64-min-ci
# - name: Run Smoke Tests (Electron)
# continue-on-error: true
# run: |
# set -e
# APP_ROOT=$(GITHUB_WORKSPACE)/VSCode-darwin-x64
# APP_NAME="`ls $APP_ROOT | head -n 1`"
# yarn smoketest --build "$APP_ROOT/$APP_NAME"
- name: Run Integration Tests (Electron)
run: .\scripts\test-integration.bat
linux:
name: Linux
@ -475,17 +86,20 @@ jobs:
sudo update-rc.d xvfb defaults
sudo service xvfb start
- uses: actions/setup-node@v2-beta
- uses: actions/setup-node@v2
with:
node-version: 12
- name: Compute node modules cache key
id: nodeModulesCacheKey
run: echo "::set-output name=value::$(node build/azure-pipelines/common/computeNodeModulesCacheKey.js)"
- name: Cache node modules
id: cacheNodeModules
uses: actions/cache@v2
with:
path: "**/node_modules"
key: ${{ runner.os }}-cacheNodeModules6-${{ hashFiles('.yarnrc', 'remote/.yarnrc', '**/yarn.lock', '!**/node_modules/**/yarn.lock', '!**/.*/**/yarn.lock') }}
restore-keys: ${{ runner.os }}-cacheNodeModules6-
key: ${{ runner.os }}-cacheNodeModules8-${{ steps.nodeModulesCacheKey.outputs.value }}
restore-keys: ${{ runner.os }}-cacheNodeModules8-
- name: Get yarn cache directory path
id: yarnCacheDirPath
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
@ -495,7 +109,7 @@ jobs:
uses: actions/cache@v2
with:
path: ${{ steps.yarnCacheDirPath.outputs.dir }}
key: ${{ runner.os }}-yarnCacheDir-${{ hashFiles('.yarnrc', 'remote/.yarnrc', '**/yarn.lock', '!**/node_modules/**/yarn.lock', '!**/.*/**/yarn.lock') }}
key: ${{ runner.os }}-yarnCacheDir-${{ steps.nodeModulesCacheKey.outputs.value }}
restore-keys: ${{ runner.os }}-yarnCacheDir-
- name: Execute yarn
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
@ -519,65 +133,6 @@ jobs:
id: electron-integration-tests
run: DISPLAY=:10 ./scripts/test-integration.sh
windows:
name: Windows
runs-on: windows-latest
env:
CHILD_CONCURRENCY: "1"
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2-beta
with:
node-version: 12
- uses: actions/setup-python@v2
with:
python-version: "2.x"
- name: Cache node modules
id: cacheNodeModules
uses: actions/cache@v2
with:
path: |
**/node_modules
!**/Release/**/*.pdb
!**/Release/**/*.ilk
!**/Release/**/*.obj
!**/Release/**/*.tlog
key: ${{ runner.os }}-cacheNodeModules6-${{ hashFiles('.yarnrc', 'remote/.yarnrc', '**/yarn.lock', '!**/node_modules/**/yarn.lock', '!**/.*/**/yarn.lock') }}
restore-keys: ${{ runner.os }}-cacheNodeModules6-
- name: Get yarn cache directory path
id: yarnCacheDirPath
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Cache yarn directory
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
uses: actions/cache@v2
with:
path: ${{ steps.yarnCacheDirPath.outputs.dir }}
key: ${{ runner.os }}-yarnCacheDir-${{ hashFiles('.yarnrc', 'remote/.yarnrc', '**/yarn.lock', '!**/node_modules/**/yarn.lock', '!**/.*/**/yarn.lock') }}
restore-keys: ${{ runner.os }}-yarnCacheDir-
- name: Execute yarn
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
env:
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
ELECTRON_SKIP_BINARY_DOWNLOAD: 1
run: yarn --frozen-lockfile --network-timeout 180000
- name: Compile and Download
run: yarn npm-run-all --max_old_space_size=4095 -lp compile "electron x64" playwright-install download-builtin-extensions
- name: Run Unit Tests (Electron)
run: .\scripts\test.bat
- name: Run Unit Tests (Browser)
run: yarn test-browser --browser chromium
- name: Run Integration Tests (Electron)
run: .\scripts\test-integration.bat
darwin:
name: macOS
runs-on: macos-latest
@ -586,17 +141,20 @@ jobs:
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2-beta
- uses: actions/setup-node@v2
with:
node-version: 12
- name: Compute node modules cache key
id: nodeModulesCacheKey
run: echo "::set-output name=value::$(node build/azure-pipelines/common/computeNodeModulesCacheKey.js)"
- name: Cache node modules
id: cacheNodeModules
uses: actions/cache@v2
with:
path: "**/node_modules"
key: ${{ runner.os }}-cacheNodeModules6-${{ hashFiles('.yarnrc', 'remote/.yarnrc', '**/yarn.lock', '!**/node_modules/**/yarn.lock', '!**/.*/**/yarn.lock') }}
restore-keys: ${{ runner.os }}-cacheNodeModules6-
key: ${{ runner.os }}-cacheNodeModules8-${{ steps.nodeModulesCacheKey.outputs.value }}
restore-keys: ${{ runner.os }}-cacheNodeModules8-
- name: Get yarn cache directory path
id: yarnCacheDirPath
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
@ -606,7 +164,7 @@ jobs:
uses: actions/cache@v2
with:
path: ${{ steps.yarnCacheDirPath.outputs.dir }}
key: ${{ runner.os }}-yarnCacheDir-${{ hashFiles('.yarnrc', 'remote/.yarnrc', '**/yarn.lock', '!**/node_modules/**/yarn.lock', '!**/.*/**/yarn.lock') }}
key: ${{ runner.os }}-yarnCacheDir-${{ steps.nodeModulesCacheKey.outputs.value }}
restore-keys: ${{ runner.os }}-yarnCacheDir-
- name: Execute yarn
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
@ -627,25 +185,39 @@ jobs:
- name: Run Integration Tests (Electron)
run: DISPLAY=:10 ./scripts/test-integration.sh
monaco:
name: Monaco Editor
hygiene:
name: Hygiene, Layering and Monaco Editor
runs-on: ubuntu-latest
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2-beta
- uses: actions/setup-node@v2
with:
node-version: 12
- name: Compute node modules cache key
id: nodeModulesCacheKey
run: echo "::set-output name=value::$(node build/azure-pipelines/common/computeNodeModulesCacheKey.js)"
- name: Cache node modules
id: cacheNodeModules
uses: actions/cache@v2
with:
path: "**/node_modules"
key: ${{ runner.os }}-cacheNodeModules6-${{ hashFiles('.yarnrc', 'remote/.yarnrc', '**/yarn.lock', '!**/node_modules/**/yarn.lock', '!**/.*/**/yarn.lock') }}
restore-keys: ${{ runner.os }}-cacheNodeModules6-
key: ${{ runner.os }}-cacheNodeModules8-${{ steps.nodeModulesCacheKey.outputs.value }}
restore-keys: ${{ runner.os }}-cacheNodeModules8-
- name: Get yarn cache directory path
id: yarnCacheDirPath
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Cache yarn directory
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
uses: actions/cache@v2
with:
path: ${{ steps.yarnCacheDirPath.outputs.dir }}
key: ${{ runner.os }}-yarnCacheDir-${{ steps.nodeModulesCacheKey.outputs.value }}
restore-keys: ${{ runner.os }}-yarnCacheDir-
- name: Execute yarn
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
env:
@ -653,8 +225,42 @@ jobs:
ELECTRON_SKIP_BINARY_DOWNLOAD: 1
run: yarn --frozen-lockfile --network-timeout 180000
- name: Run Hygiene Checks
run: yarn gulp hygiene
- name: Run Valid Layers Checks
run: yarn valid-layers-check
- name: Run Monaco Editor Checks
run: yarn monaco-compile-check
- name: Editor Distro & ESM Bundle
run: yarn gulp editor-esm-bundle
- name: Typings validation prep
run: |
mkdir typings-test
- name: Typings validation
working-directory: ./typings-test
run: |
yarn init -yp
../node_modules/.bin/tsc --init
echo "import '../out-monaco-editor-core';" > a.ts
../node_modules/.bin/tsc --noEmit
- name: Webpack Editor
working-directory: ./test/monaco
run: yarn run bundle
- name: Compile Editor Tests
working-directory: ./test/monaco
run: yarn run compile
- name: Download Playwright
run: yarn playwright-install
- name: Run Editor Tests
timeout-minutes: 5
working-directory: ./test/monaco
run: yarn test

22
.gitignore vendored
View file

@ -5,26 +5,8 @@ Thumbs.db
node_modules/
.build/
extensions/**/dist/
out/
out-build/
out-editor/
out-editor-src/
out-editor-build/
out-editor-esm/
out-editor-esm-bundle/
out-editor-min/
out-monaco-editor-core/
out-vscode/
out-vscode-min/
out-vscode-reh/
out-vscode-reh-min/
out-vscode-reh-pkg/
out-vscode-reh-web/
out-vscode-reh-web-min/
out-vscode-reh-web-pkg/
out-vscode-web/
out-vscode-web-min/
out-vscode-web-pkg/
/out*/
/extensions/**/out/
src/vs/server
resources/server
build/node_modules

View file

@ -3,7 +3,6 @@
// for the documentation about the extensions.json format
"recommendations": [
"dbaeumer.vscode-eslint",
"EditorConfig.EditorConfig",
"msjsdiag.debugger-for-chrome"
"EditorConfig.EditorConfig"
]
}

22
.vscode/launch.json vendored
View file

@ -203,7 +203,7 @@
{
"type": "pwa-chrome",
"request": "launch",
"name": "Launch VS Code",
"name": "Launch VS Code Internal",
"windows": {
"runtimeExecutable": "${workspaceFolder}/scripts/code.bat"
},
@ -235,7 +235,9 @@
"${workspaceFolder}/out/**/*.js"
],
"browserLaunchLocation": "workspace",
"preLaunchTask": "Ensure Prelaunch Dependencies",
"presentation": {
"hidden": true,
}
},
{
"type": "node",
@ -472,7 +474,7 @@
"name": "VS Code",
"stopAll": true,
"configurations": [
"Launch VS Code",
"Launch VS Code Internal",
"Attach to Main Process",
"Attach to Extension Host",
"Attach to Shared Process",
@ -486,7 +488,7 @@
{
"name": "Search and Renderer processes",
"configurations": [
"Launch VS Code",
"Launch VS Code Internal",
"Attach to Search Process"
],
"presentation": {
@ -497,7 +499,7 @@
{
"name": "Renderer and Extension Host processes",
"configurations": [
"Launch VS Code",
"Launch VS Code Internal",
"Attach to Extension Host"
],
"presentation": {
@ -526,6 +528,14 @@
"group": "1_vscode",
"order": 2
}
}
},
{
"name": "Launch VS Code",
"stopAll": true,
"configurations": [
"Launch VS Code Internal",
],
"preLaunchTask": "Ensure Prelaunch Dependencies"
},
]
}

View file

@ -83,6 +83,24 @@
"value": "$repos assignee:@me is:open milestone:\"Backlog Candidates\"",
"editable": false
},
{
"kind": 1,
"language": "markdown",
"value": "### Personal Inbox\n",
"editable": true
},
{
"kind": 1,
"language": "markdown",
"value": "\n#### Missing Type label",
"editable": true
},
{
"kind": 2,
"language": "github-issues",
"value": "$repos assignee:@me is:open type:issue -label:bug -label:\"needs more info\" -label:feature-request -label:under-discussion -label:debt -label:plan-item -label:upstream",
"editable": true
},
{
"kind": 1,
"language": "markdown",

View file

@ -1 +1 @@
2020-12-17T07:59:24.060Z
2021-01-07T09:53:10.404Z

View file

@ -0,0 +1,25 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
const fs = require("fs");
const path = require("path");
const crypto = require("crypto");
const { dirs } = require('../../npm/dirs');
const ROOT = path.join(__dirname, '../../../');
const shasum = crypto.createHash('sha1');
shasum.update(fs.readFileSync(path.join(ROOT, 'build/.cachesalt')));
shasum.update(fs.readFileSync(path.join(ROOT, '.yarnrc')));
shasum.update(fs.readFileSync(path.join(ROOT, 'remote/.yarnrc')));
// Add `yarn.lock` files
for (let dir of dirs) {
const yarnLockPath = path.join(ROOT, dir, 'yarn.lock');
shasum.update(fs.readFileSync(yarnLockPath));
}
// Add any other command line arguments
for (let i = 2; i < process.argv.length; i++) {
shasum.update(process.argv[i]);
}
process.stdout.write(shasum.digest('hex'));

View file

@ -0,0 +1,32 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as fs from 'fs';
import * as path from 'path';
import * as crypto from 'crypto';
const { dirs } = require('../../npm/dirs');
const ROOT = path.join(__dirname, '../../../');
const shasum = crypto.createHash('sha1');
shasum.update(fs.readFileSync(path.join(ROOT, 'build/.cachesalt')));
shasum.update(fs.readFileSync(path.join(ROOT, '.yarnrc')));
shasum.update(fs.readFileSync(path.join(ROOT, 'remote/.yarnrc')));
// Add `yarn.lock` files
for (let dir of dirs) {
const yarnLockPath = path.join(ROOT, dir, 'yarn.lock');
shasum.update(fs.readFileSync(yarnLockPath));
}
// Add any other command line arguments
for (let i = 2; i < process.argv.length; i++) {
shasum.update(process.argv[i]);
}
process.stdout.write(shasum.digest('hex'));

View file

@ -10,8 +10,8 @@ git clone --depth 1 https://github.com/microsoft/vscode-node-debug2.git
git clone --depth 1 https://github.com/microsoft/vscode-node-debug.git
git clone --depth 1 https://github.com/microsoft/vscode-html-languageservice.git
git clone --depth 1 https://github.com/microsoft/vscode-json-languageservice.git
node $BUILD_SOURCESDIRECTORY/build/node_modules/.bin/vscode-telemetry-extractor --sourceDir $BUILD_SOURCESDIRECTORY --excludedDir $BUILD_SOURCESDIRECTORY/extensions --outputDir . --applyEndpoints
node $BUILD_SOURCESDIRECTORY/build/node_modules/.bin/vscode-telemetry-extractor --config $BUILD_SOURCESDIRECTORY/build/azure-pipelines/common/telemetry-config.json -o .
node $BUILD_SOURCESDIRECTORY/node_modules/.bin/vscode-telemetry-extractor --sourceDir $BUILD_SOURCESDIRECTORY --excludedDir $BUILD_SOURCESDIRECTORY/extensions --outputDir . --applyEndpoints
node $BUILD_SOURCESDIRECTORY/node_modules/.bin/vscode-telemetry-extractor --config $BUILD_SOURCESDIRECTORY/build/azure-pipelines/common/telemetry-config.json -o .
mkdir -p $BUILD_SOURCESDIRECTORY/.build/telemetry
mv declarations-resolved.json $BUILD_SOURCESDIRECTORY/.build/telemetry/telemetry-core.json
mv config-resolved.json $BUILD_SOURCESDIRECTORY/.build/telemetry/telemetry-extensions.json

View file

@ -0,0 +1,40 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
const fs = require("fs");
const path = require("path");
if (process.argv.length !== 3) {
console.error('Usage: node listNodeModules.js OUTPUT_FILE');
process.exit(-1);
}
const ROOT = path.join(__dirname, '../../../');
function findNodeModulesFiles(location, inNodeModules, result) {
const entries = fs.readdirSync(path.join(ROOT, location));
for (const entry of entries) {
const entryPath = `${location}/${entry}`;
if (/(^\/out)|(^\/src$)|(^\/.git$)|(^\/.build$)/.test(entryPath)) {
continue;
}
let stat;
try {
stat = fs.statSync(path.join(ROOT, entryPath));
}
catch (err) {
continue;
}
if (stat.isDirectory()) {
findNodeModulesFiles(entryPath, inNodeModules || (entry === 'node_modules'), result);
}
else {
if (inNodeModules) {
result.push(entryPath.substr(1));
}
}
}
}
const result = [];
findNodeModulesFiles('', false, result);
fs.writeFileSync(process.argv[2], result.join('\n') + '\n');

View file

@ -0,0 +1,46 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as fs from 'fs';
import * as path from 'path';
if (process.argv.length !== 3) {
console.error('Usage: node listNodeModules.js OUTPUT_FILE');
process.exit(-1);
}
const ROOT = path.join(__dirname, '../../../');
function findNodeModulesFiles(location: string, inNodeModules: boolean, result: string[]) {
const entries = fs.readdirSync(path.join(ROOT, location));
for (const entry of entries) {
const entryPath = `${location}/${entry}`;
if (/(^\/out)|(^\/src$)|(^\/.git$)|(^\/.build$)/.test(entryPath)) {
continue;
}
let stat: fs.Stats;
try {
stat = fs.statSync(path.join(ROOT, entryPath));
} catch (err) {
continue;
}
if (stat.isDirectory()) {
findNodeModulesFiles(entryPath, inNodeModules || (entry === 'node_modules'), result);
} else {
if (inNodeModules) {
result.push(entryPath.substr(1));
}
}
}
}
const result: string[] = [];
findNodeModulesFiles('', false, result);
fs.writeFileSync(process.argv[2], result.join('\n') + '\n');

View file

@ -7,9 +7,14 @@ steps:
inputs:
versionSpec: "1.x"
- script: |
mkdir -p .build
node build/azure-pipelines/common/computeNodeModulesCacheKey.js > .build/yarnlockhash
displayName: Prepare yarn cache flags
- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1
inputs:
keyfile: ".yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock"
keyfile: ".build/yarnlockhash"
targetfolder: "**/node_modules, !**/node_modules/**/node_modules"
vstsFeed: "vscode-build-cache"
@ -20,7 +25,7 @@ steps:
- task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1
inputs:
keyfile: ".yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock"
keyfile: ".build/yarnlockhash"
targetfolder: "**/node_modules, !**/node_modules/**/node_modules"
vstsFeed: "vscode-build-cache"
condition: and(succeeded(), ne(variables['CacheRestored'], 'true'))

View file

@ -48,27 +48,35 @@ steps:
displayName: Merge distro
- script: |
npx https://aka.ms/enablesecurefeed standAlone
displayName: Switch to Terrapin packages
timeoutInMinutes: 5
condition: and(succeeded(), eq(variables['ENABLE_TERRAPIN'], 'true'))
- script: |
echo -n $(VSCODE_ARCH) > .build/arch
echo -n $ENABLE_TERRAPIN > .build/terrapin
mkdir -p .build
node build/azure-pipelines/common/computeNodeModulesCacheKey.js $VSCODE_ARCH $ENABLE_TERRAPIN > .build/yarnlockhash
displayName: Prepare yarn cache flags
- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1
- task: Cache@2
inputs:
keyfile: ".build/arch, .build/terrapin, build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock"
targetfolder: "**/node_modules, !**/node_modules/**/node_modules"
vstsFeed: "npm-vscode"
key: 'nodeModules | $(Agent.OS) | .build/yarnlockhash'
path: .build/node_modules_cache
cacheHitVar: NODE_MODULES_RESTORED
displayName: Restore node_modules cache
- script: |
set -e
tar -xzf .build/node_modules_cache/cache.tgz
condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true'))
displayName: Extract node_modules cache
- script: |
set -e
npm install -g node-gyp@latest
node-gyp --version
displayName: Update node-gyp
condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'))
- script: |
npx https://aka.ms/enablesecurefeed standAlone
timeoutInMinutes: 5
condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), eq(variables['ENABLE_TERRAPIN'], 'true'))
displayName: Switch to Terrapin packages
- script: |
set -e
@ -88,14 +96,15 @@ steps:
ELECTRON_SKIP_BINARY_DOWNLOAD: 1
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
displayName: Install dependencies
condition: and(succeeded(), ne(variables['CacheRestored'], 'true'))
condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'))
- task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1
inputs:
keyfile: ".build/arch, .build/terrapin, build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock"
targetfolder: "**/node_modules, !**/node_modules/**/node_modules"
vstsFeed: "npm-vscode"
condition: and(succeeded(), ne(variables['CacheRestored'], 'true'))
- script: |
set -e
node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt
mkdir -p .build/node_modules_cache
tar -czf .build/node_modules_cache/cache.tgz --files-from .build/node_modules_list.txt
condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'))
displayName: Create node_modules archive
- script: |
set -e
@ -108,7 +117,7 @@ steps:
cd ./node_modules/keytar
node-gyp rebuild
displayName: Rebuild native modules for ARM64
condition: eq(variables['VSCODE_ARCH'], 'arm64')
condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'arm64'))
- script: |
set -e
@ -166,7 +175,7 @@ steps:
set -e
yarn --cwd test/integration/browser compile
displayName: Compile integration tests
condition: and(succeeded(), eq(variables['CacheRestored'], 'true'), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false'))
condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true'), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false'))
- script: |
# Figure out the full absolute path of the product we just built
@ -205,7 +214,7 @@ steps:
set -e
yarn --cwd test/smoke compile
displayName: Compile smoke tests
condition: and(succeeded(), eq(variables['CacheRestored'], 'true'), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false'))
condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true'), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false'))
- script: |
set -e
@ -239,7 +248,7 @@ steps:
inputs:
testResultsFiles: "*-results.xml"
searchFolder: "$(Build.ArtifactStagingDirectory)/test-results"
condition: succeededOrFailed()
condition: and(succeededOrFailed(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false'))
- script: |
set -e

View file

@ -16,9 +16,14 @@ steps:
inputs:
versionSpec: "1.x"
- script: |
mkdir -p .build
node build/azure-pipelines/common/computeNodeModulesCacheKey.js > .build/yarnlockhash
displayName: Prepare yarn cache flags
- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1
inputs:
keyfile: ".yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock"
keyfile: ".build/yarnlockhash"
targetfolder: "**/node_modules, !**/node_modules/**/node_modules"
vstsFeed: "vscode-build-cache"
@ -29,7 +34,7 @@ steps:
- task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1
inputs:
keyfile: ".yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock"
keyfile: ".build/yarnlockhash"
targetfolder: "**/node_modules, !**/node_modules/**/node_modules"
vstsFeed: "vscode-build-cache"
condition: and(succeeded(), ne(variables['CacheRestored'], 'true'))

View file

@ -51,21 +51,28 @@ steps:
displayName: Merge distro
- script: |
npx https://aka.ms/enablesecurefeed standAlone
displayName: Switch to Terrapin packages
timeoutInMinutes: 5
condition: and(succeeded(), eq(variables['ENABLE_TERRAPIN'], 'true'))
- script: |
echo -n "alpine" > .build/arch
echo -n $ENABLE_TERRAPIN > .build/terrapin
mkdir -p .build
node build/azure-pipelines/common/computeNodeModulesCacheKey.js "alpine" $ENABLE_TERRAPIN > .build/yarnlockhash
displayName: Prepare yarn cache flags
- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1
- task: Cache@2
inputs:
keyfile: ".build/arch, .build/terrapin, build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock"
targetfolder: "**/node_modules, !**/node_modules/**/node_modules"
vstsFeed: "npm-vscode"
key: 'nodeModules | $(Agent.OS) | .build/yarnlockhash'
path: .build/node_modules_cache
cacheHitVar: NODE_MODULES_RESTORED
displayName: Restore node_modules cache
- script: |
set -e
tar -xzf .build/node_modules_cache/cache.tgz
condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true'))
displayName: Extract node_modules cache
- script: |
npx https://aka.ms/enablesecurefeed standAlone
timeoutInMinutes: 5
condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), eq(variables['ENABLE_TERRAPIN'], 'true'))
displayName: Switch to Terrapin packages
- script: |
set -e
@ -81,14 +88,15 @@ steps:
ELECTRON_SKIP_BINARY_DOWNLOAD: 1
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
displayName: Install dependencies
condition: and(succeeded(), ne(variables['CacheRestored'], 'true'))
condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'))
- task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1
inputs:
keyfile: ".build/arch, .build/terrapin, build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock"
targetfolder: "**/node_modules, !**/node_modules/**/node_modules"
vstsFeed: "npm-vscode"
condition: and(succeeded(), ne(variables['CacheRestored'], 'true'))
- script: |
set -e
node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt
mkdir -p .build/node_modules_cache
tar -czf .build/node_modules_cache/cache.tgz --files-from .build/node_modules_list.txt
condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'))
displayName: Create node_modules archive
- script: |
set -e

View file

@ -42,28 +42,35 @@ steps:
displayName: Merge distro
- script: |
npx https://aka.ms/enablesecurefeed standAlone
displayName: Switch to Terrapin packages
timeoutInMinutes: 5
condition: and(succeeded(), eq(variables['ENABLE_TERRAPIN'], 'true'))
- script: |
echo -n $(VSCODE_ARCH) > .build/arch
echo -n $ENABLE_TERRAPIN > .build/terrapin
mkdir -p .build
node build/azure-pipelines/common/computeNodeModulesCacheKey.js $VSCODE_ARCH $ENABLE_TERRAPIN > .build/yarnlockhash
displayName: Prepare yarn cache flags
- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1
- task: Cache@2
inputs:
keyfile: ".build/arch, .build/terrapin, build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock"
targetfolder: "**/node_modules, !**/node_modules/**/node_modules"
vstsFeed: "npm-vscode"
key: 'nodeModules | $(Agent.OS) | .build/yarnlockhash'
path: .build/node_modules_cache
cacheHitVar: NODE_MODULES_RESTORED
displayName: Restore node_modules cache
- script: |
set -e
tar -xzf .build/node_modules_cache/cache.tgz
condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true'))
displayName: Extract node_modules cache
- script: |
set -e
npm install -g node-gyp@latest
node-gyp --version
displayName: Update node-gyp
condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'))
condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), eq(variables['VSCODE_ARCH'], 'x64'))
- script: |
npx https://aka.ms/enablesecurefeed standAlone
timeoutInMinutes: 5
condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), eq(variables['ENABLE_TERRAPIN'], 'true'))
displayName: Switch to Terrapin packages
- script: |
set -e
@ -92,14 +99,15 @@ steps:
ELECTRON_SKIP_BINARY_DOWNLOAD: 1
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
displayName: Install dependencies
condition: and(succeeded(), ne(variables['CacheRestored'], 'true'))
condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'))
- task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1
inputs:
keyfile: ".build/arch, .build/terrapin, build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock"
targetfolder: "**/node_modules, !**/node_modules/**/node_modules"
vstsFeed: "npm-vscode"
condition: and(succeeded(), ne(variables['CacheRestored'], 'true'))
- script: |
set -e
node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt
mkdir -p .build/node_modules_cache
tar -czf .build/node_modules_cache/cache.tgz --files-from .build/node_modules_list.txt
condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'))
displayName: Create node_modules archive
- script: |
set -e
@ -144,7 +152,7 @@ steps:
set -e
yarn --cwd test/integration/browser compile
displayName: Compile integration tests
condition: and(succeeded(), eq(variables['CacheRestored'], 'true'), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false'))
condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true'), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false'))
- script: |
# Figure out the full absolute path of the product we just built

View file

@ -7,9 +7,93 @@ schedules:
include:
- master
parameters:
- name: VSCODE_QUALITY
displayName: Quality
type: string
default: insider
values:
- exploration
- insider
- stable
- name: ENABLE_TERRAPIN
displayName: "Enable Terrapin"
type: boolean
default: true
- name: VSCODE_BUILD_WIN32
displayName: "🎯 Windows x64"
type: boolean
default: true
- name: VSCODE_BUILD_WIN32_32BIT
displayName: "🎯 Windows ia32"
type: boolean
default: true
- name: VSCODE_BUILD_WIN32_ARM64
displayName: "🎯 Windows arm64"
type: boolean
default: true
- name: VSCODE_BUILD_LINUX
displayName: "🎯 Linux x64"
type: boolean
default: true
- name: VSCODE_BUILD_LINUX_ARM64
displayName: "🎯 Linux arm64"
type: boolean
default: true
- name: VSCODE_BUILD_LINUX_ARMHF
displayName: "🎯 Linux armhf"
type: boolean
default: true
- name: VSCODE_BUILD_LINUX_ALPINE
displayName: "🎯 Alpine Linux"
type: boolean
default: true
- name: VSCODE_BUILD_MACOS
displayName: "🎯 macOS x64"
type: boolean
default: true
- name: VSCODE_BUILD_MACOS_ARM64
displayName: "🎯 macOS arm64"
type: boolean
default: true
- name: VSCODE_BUILD_WEB
displayName: "🎯 Web"
type: boolean
default: true
- name: VSCODE_PUBLISH
displayName: "Publish to builds.code.visualstudio.com"
type: boolean
default: true
- name: VSCODE_RELEASE
displayName: "Release build if successful"
type: boolean
default: false
- name: VSCODE_COMPILE_ONLY
displayName: "Run Compile stage exclusively"
type: boolean
default: false
- name: VSCODE_STEP_ON_IT
displayName: "Skip tests"
type: boolean
default: false
variables:
${{ if and(eq(variables['VSCODE_PUBLISH'], ''), or(eq(variables['Build.Reason'], 'IndividualCI'), eq(variables['Build.Reason'], 'BatchedCI'))) }}:
VSCODE_PUBLISH: "false"
- name: ENABLE_TERRAPIN
value: ${{ eq(parameters.ENABLE_TERRAPIN, true) }}
- name: VSCODE_BUILD_STAGE_WINDOWS
value: ${{ or(eq(parameters.VSCODE_BUILD_WIN32, true), eq(parameters.VSCODE_BUILD_WIN32_32BIT, true), eq(parameters.VSCODE_BUILD_WIN32_ARM64, true)) }}
- name: VSCODE_BUILD_STAGE_LINUX
value: ${{ or(eq(parameters.VSCODE_BUILD_LINUX, true), eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true), eq(parameters.VSCODE_BUILD_LINUX_ARM64, true), eq(parameters.VSCODE_BUILD_LINUX_ALPINE, true), eq(parameters.VSCODE_BUILD_WEB, true)) }}
- name: VSCODE_BUILD_STAGE_MACOS
value: ${{ or(eq(parameters.VSCODE_BUILD_MACOS, true), eq(parameters.VSCODE_BUILD_MACOS_ARM64, true)) }}
- name: VSCODE_CIBUILD
value: ${{ in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI') }}
- name: VSCODE_PUBLISH
value: ${{ and(eq(parameters.VSCODE_PUBLISH, true), eq(variables.VSCODE_CIBUILD, false)) }}
- name: VSCODE_SCHEDULEDBUILD
value: ${{ eq(variables['Build.Reason'], 'Schedule') }}
- name: VSCODE_STEP_ON_IT
value: ${{ eq(parameters.VSCODE_STEP_ON_IT, true) }}
resources:
containers:
@ -30,166 +114,165 @@ stages:
- stage: Compile
jobs:
- job: Compile
pool:
vmImage: "Ubuntu-18.04"
container: vscode-x64
pool: compile
variables:
VSCODE_ARCH: x64
steps:
- template: product-compile.yml
- job: Hygiene
pool:
vmImage: "Ubuntu-18.04"
container: vscode-x64
variables:
VSCODE_ARCH: x64
steps:
- template: product-hygiene.yml
- stage: Windows
dependsOn:
- Compile
condition: and(succeeded(), eq(variables['VSCODE_COMPILE_ONLY'], 'false'))
pool:
vmImage: VS2017-Win2016
jobs:
- job: Windows
condition: and(succeeded(), eq(variables['VSCODE_BUILD_WIN32'], 'true'))
timeoutInMinutes: 90
variables:
VSCODE_ARCH: x64
steps:
- template: win32/product-build-win32.yml
- ${{ if and(eq(parameters.VSCODE_COMPILE_ONLY, false), eq(variables['VSCODE_BUILD_STAGE_WINDOWS'], true)) }}:
- stage: Windows
dependsOn:
- Compile
pool:
vmImage: VS2017-Win2016
jobs:
- job: Windows32
condition: and(succeeded(), eq(variables['VSCODE_BUILD_WIN32_32BIT'], 'true'), ne(variables['VSCODE_PUBLISH'], 'false'))
timeoutInMinutes: 90
variables:
VSCODE_ARCH: ia32
steps:
- template: win32/product-build-win32.yml
- ${{ if eq(parameters.VSCODE_BUILD_WIN32, true) }}:
- job: Windows
timeoutInMinutes: 90
variables:
VSCODE_ARCH: x64
steps:
- template: win32/product-build-win32.yml
- job: WindowsARM64
condition: and(succeeded(), eq(variables['VSCODE_BUILD_WIN32_ARM64'], 'true'), ne(variables['VSCODE_PUBLISH'], 'false'))
timeoutInMinutes: 90
variables:
VSCODE_ARCH: arm64
steps:
- template: win32/product-build-win32.yml
- ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_WIN32_32BIT, true)) }}:
- job: Windows32
timeoutInMinutes: 90
variables:
VSCODE_ARCH: ia32
steps:
- template: win32/product-build-win32.yml
- stage: Linux
dependsOn:
- Compile
condition: and(succeeded(), eq(variables['VSCODE_COMPILE_ONLY'], 'false'))
pool:
vmImage: "Ubuntu-18.04"
jobs:
- job: Linux
condition: and(succeeded(), eq(variables['VSCODE_BUILD_LINUX'], 'true'))
container: vscode-x64
variables:
VSCODE_ARCH: x64
NPM_ARCH: x64
steps:
- template: linux/product-build-linux.yml
- ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_WIN32_ARM64, true)) }}:
- job: WindowsARM64
timeoutInMinutes: 90
variables:
VSCODE_ARCH: arm64
steps:
- template: win32/product-build-win32.yml
- job: LinuxSnap
dependsOn:
- ${{ if and(eq(parameters.VSCODE_COMPILE_ONLY, false), eq(variables['VSCODE_BUILD_STAGE_LINUX'], true)) }}:
- stage: Linux
dependsOn:
- Compile
pool:
vmImage: "Ubuntu-18.04"
jobs:
- ${{ if eq(parameters.VSCODE_BUILD_LINUX, true) }}:
- job: Linux
container: vscode-x64
variables:
VSCODE_ARCH: x64
NPM_ARCH: x64
steps:
- template: linux/product-build-linux.yml
- ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_LINUX, true)) }}:
- job: LinuxSnap
dependsOn:
- Linux
container: snapcraft
variables:
VSCODE_ARCH: x64
steps:
- template: linux/snap-build-linux.yml
- ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true)) }}:
- job: LinuxArmhf
container: vscode-armhf
variables:
VSCODE_ARCH: armhf
NPM_ARCH: armv7l
steps:
- template: linux/product-build-linux.yml
- ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true)) }}:
- job: LinuxSnapArmhf
dependsOn:
- LinuxArmhf
container: snapcraft
variables:
VSCODE_ARCH: armhf
steps:
- template: linux/snap-build-linux.yml
- ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_LINUX_ARM64, true)) }}:
- job: LinuxArm64
container: vscode-arm64
variables:
VSCODE_ARCH: arm64
NPM_ARCH: arm64
steps:
- template: linux/product-build-linux.yml
- ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_LINUX_ARM64, true)) }}:
- job: LinuxSnapArm64
dependsOn:
- LinuxArm64
container: snapcraft
variables:
VSCODE_ARCH: arm64
steps:
- template: linux/snap-build-linux.yml
- ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_LINUX_ALPINE, true)) }}:
- job: LinuxAlpine
steps:
- template: linux/product-build-alpine.yml
- ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_WEB, true)) }}:
- job: LinuxWeb
variables:
VSCODE_ARCH: x64
steps:
- template: web/product-build-web.yml
- ${{ if and(eq(parameters.VSCODE_COMPILE_ONLY, false), eq(variables['VSCODE_BUILD_STAGE_MACOS'], true)) }}:
- stage: macOS
dependsOn:
- Compile
pool:
vmImage: macOS-latest
jobs:
- ${{ if eq(parameters.VSCODE_BUILD_MACOS, true) }}:
- job: macOS
timeoutInMinutes: 90
variables:
VSCODE_ARCH: x64
steps:
- template: darwin/product-build-darwin.yml
- ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_MACOS_ARM64, true)) }}:
- job: macOSARM64
timeoutInMinutes: 90
variables:
VSCODE_ARCH: arm64
steps:
- template: darwin/product-build-darwin.yml
- ${{ if and(eq(variables['VSCODE_PUBLISH'], true), eq(parameters.VSCODE_COMPILE_ONLY, false)) }}:
- stage: Publish
dependsOn:
- ${{ if eq(variables['VSCODE_BUILD_STAGE_WINDOWS'], true) }}:
- Windows
- ${{ if eq(variables['VSCODE_BUILD_STAGE_LINUX'], true) }}:
- Linux
condition: and(succeeded(), eq(variables['VSCODE_BUILD_LINUX'], 'true'), ne(variables['VSCODE_PUBLISH'], 'false'))
container: snapcraft
variables:
VSCODE_ARCH: x64
steps:
- template: linux/snap-build-linux.yml
- ${{ if eq(variables['VSCODE_BUILD_STAGE_MACOS'], true) }}:
- macOS
condition: succeededOrFailed()
pool:
vmImage: "Ubuntu-18.04"
jobs:
- job: SyncMooncake
displayName: Sync Mooncake
steps:
- template: sync-mooncake.yml
- job: LinuxArmhf
condition: and(succeeded(), eq(variables['VSCODE_BUILD_LINUX_ARMHF'], 'true'), ne(variables['VSCODE_PUBLISH'], 'false'))
container: vscode-armhf
variables:
VSCODE_ARCH: armhf
NPM_ARCH: armv7l
steps:
- template: linux/product-build-linux.yml
- job: LinuxSnapArmhf
dependsOn:
- LinuxArmhf
condition: and(succeeded(), eq(variables['VSCODE_BUILD_LINUX_ARMHF'], 'true'), ne(variables['VSCODE_PUBLISH'], 'false'))
container: snapcraft
variables:
VSCODE_ARCH: armhf
steps:
- template: linux/snap-build-linux.yml
- job: LinuxArm64
condition: and(succeeded(), eq(variables['VSCODE_BUILD_LINUX_ARM64'], 'true'), ne(variables['VSCODE_PUBLISH'], 'false'))
container: vscode-arm64
variables:
VSCODE_ARCH: arm64
NPM_ARCH: arm64
steps:
- template: linux/product-build-linux.yml
- job: LinuxSnapArm64
dependsOn:
- LinuxArm64
condition: and(succeeded(), eq(variables['VSCODE_BUILD_LINUX_ARM64'], 'true'), ne(variables['VSCODE_PUBLISH'], 'false'))
container: snapcraft
variables:
VSCODE_ARCH: arm64
steps:
- template: linux/snap-build-linux.yml
- job: LinuxAlpine
condition: and(succeeded(), eq(variables['VSCODE_BUILD_LINUX_ALPINE'], 'true'), ne(variables['VSCODE_PUBLISH'], 'false'))
steps:
- template: linux/product-build-alpine.yml
- job: LinuxWeb
condition: and(succeeded(), eq(variables['VSCODE_BUILD_WEB'], 'true'), ne(variables['VSCODE_PUBLISH'], 'false'))
variables:
VSCODE_ARCH: x64
steps:
- template: web/product-build-web.yml
- stage: macOS
dependsOn:
- Compile
condition: and(succeeded(), eq(variables['VSCODE_COMPILE_ONLY'], 'false'))
pool:
vmImage: macOS-latest
jobs:
- job: macOS
condition: and(succeeded(), eq(variables['VSCODE_BUILD_MACOS'], 'true'))
timeoutInMinutes: 90
variables:
VSCODE_ARCH: x64
steps:
- template: darwin/product-build-darwin.yml
- job: macOSARM64
condition: and(succeeded(), eq(variables['VSCODE_BUILD_MACOS_ARM64'], 'true'), ne(variables['VSCODE_PUBLISH'], 'false'))
timeoutInMinutes: 90
variables:
VSCODE_ARCH: arm64
steps:
- template: darwin/product-build-darwin.yml
- stage: Publish
dependsOn:
- Windows
- Linux
- macOS
condition: and(succeededOrFailed(), eq(variables['VSCODE_COMPILE_ONLY'], 'false'), ne(variables['VSCODE_PUBLISH'], 'false'))
pool:
vmImage: "Ubuntu-18.04"
jobs:
- job: SyncMooncake
displayName: Sync Mooncake
steps:
- template: sync-mooncake.yml
- job: ReleaseBuild
condition: and(succeeded(), or(eq(variables['VSCODE_RELEASE'], 'true'), and(or(eq(variables['VSCODE_QUALITY'], 'insider'), eq(variables['VSCODE_QUALITY'], 'exploration')), eq(variables['Build.Reason'], 'Schedule'))))
displayName: Release Build
steps:
- template: release.yml
- ${{ if or(eq(parameters.VSCODE_RELEASE, true), and(in(parameters.VSCODE_QUALITY, 'insider', 'exploration'), eq(variables['VSCODE_SCHEDULEDBUILD'], true))) }}:
- job: ReleaseBuild
displayName: Release Build
steps:
- template: release.yml

View file

@ -1,7 +1,7 @@
steps:
- task: NodeTool@0
inputs:
versionSpec: "12.18.3"
versionSpec: "12.x"
- task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2
inputs:
@ -30,39 +30,40 @@ steps:
git pull --no-rebase https://github.com/$(VSCODE_MIXIN_REPO).git $(node -p "require('./package.json').distro")
displayName: Merge distro
- script: |
npx https://aka.ms/enablesecurefeed standAlone
displayName: Switch to Terrapin packages
timeoutInMinutes: 5
condition: and(succeeded(), eq(variables['ENABLE_TERRAPIN'], 'true'))
- script: |
mkdir -p .build
echo -n $(VSCODE_ARCH) > .build/arch
echo -n $ENABLE_TERRAPIN > .build/terrapin
node build/azure-pipelines/common/computeNodeModulesCacheKey.js $VSCODE_ARCH $ENABLE_TERRAPIN > .build/yarnlockhash
displayName: Prepare yarn cache flags
- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1
# using `genericNodeModules` instead of `nodeModules` here to avoid sharing the cache with builds running inside containers
- task: Cache@2
inputs:
keyfile: ".build/arch, .build/terrapin, build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock"
targetfolder: "**/node_modules, !**/node_modules/**/node_modules"
vstsFeed: "npm-vscode"
key: 'genericNodeModules | $(Agent.OS) | .build/yarnlockhash'
path: .build/node_modules_cache
cacheHitVar: NODE_MODULES_RESTORED
displayName: Restore node_modules cache
- script: |
set -e
export npm_config_arch=$(NPM_ARCH)
tar -xzf .build/node_modules_cache/cache.tgz
condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true'))
displayName: Extract node_modules cache
if [ -z "$CC" ] || [ -z "$CXX" ]; then
export CC=$(which gcc-5)
export CXX=$(which g++-5)
fi
- script: |
npx https://aka.ms/enablesecurefeed standAlone
timeoutInMinutes: 5
condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), eq(variables['ENABLE_TERRAPIN'], 'true'))
displayName: Switch to Terrapin packages
if [ "$VSCODE_ARCH" == "x64" ]; then
export VSCODE_REMOTE_CC=$(which gcc-4.8)
export VSCODE_REMOTE_CXX=$(which g++-4.8)
export VSCODE_REMOTE_NODE_GYP=$(which node-gyp)
fi
- script: |
set -e
sudo apt update -y
sudo apt install -y build-essential pkg-config libx11-dev libx11-xcb-dev libxkbfile-dev libsecret-1-dev libnotify-bin
displayName: Install build tools
condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'))
- script: |
set -e
for i in {1..3}; do # try 3 times, for Terrapin
yarn --frozen-lockfile && break
if [ $i -eq 3 ]; then
@ -75,14 +76,15 @@ steps:
ELECTRON_SKIP_BINARY_DOWNLOAD: 1
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
displayName: Install dependencies
condition: and(succeeded(), ne(variables['CacheRestored'], 'true'))
condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'))
- task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1
inputs:
keyfile: ".build/arch, .build/terrapin, build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock"
targetfolder: "**/node_modules, !**/node_modules/**/node_modules"
vstsFeed: "npm-vscode"
condition: and(succeeded(), ne(variables['CacheRestored'], 'true'))
- script: |
set -e
node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt
mkdir -p .build/node_modules_cache
tar -czf .build/node_modules_cache/cache.tgz --files-from .build/node_modules_list.txt
condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'))
displayName: Create node_modules archive
# Mixin must run before optimize, because the CSS loader will inline small SVGs
- script: |
@ -92,12 +94,8 @@ steps:
- script: |
set -e
yarn gulp compile-build
yarn gulp compile-extensions-build
yarn gulp minify-vscode
yarn gulp minify-vscode-reh
yarn gulp minify-vscode-reh-web
displayName: Compile
yarn npm-run-all -lp core-ci extensions-ci hygiene eslint valid-layers-check
displayName: Compile & Hygiene
- script: |
set -e
@ -106,6 +104,19 @@ steps:
displayName: Upload sourcemaps
condition: and(succeeded(), ne(variables['VSCODE_PUBLISH'], 'false'))
- script: |
set -
./build/azure-pipelines/common/extract-telemetry.sh
displayName: Extract Telemetry
condition: and(succeeded(), ne(variables['VSCODE_PUBLISH'], 'false'))
- script: |
set -e
AZURE_WEBVIEW_STORAGE_ACCESS_KEY="$(vscode-webview-storage-key)" \
./build/azure-pipelines/common/publish-webview.sh
displayName: Publish Webview
condition: and(succeeded(), ne(variables['VSCODE_PUBLISH'], 'false'))
- script: |
set -e
VERSION=`node -p "require(\"./package.json\").version"`

View file

@ -1,92 +0,0 @@
steps:
- task: NodeTool@0
inputs:
versionSpec: "12.18.3"
- task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2
inputs:
versionSpec: "1.x"
- task: AzureKeyVault@1
displayName: "Azure Key Vault: Get Secrets"
inputs:
azureSubscription: "vscode-builds-subscription"
KeyVaultName: vscode
- script: |
set -e
cat << EOF > ~/.netrc
machine github.com
login vscode
password $(github-distro-mixin-password)
EOF
git config user.email "vscode@microsoft.com"
git config user.name "VSCode"
displayName: Prepare tooling
- script: |
set -e
git pull --no-rebase https://github.com/$(VSCODE_MIXIN_REPO).git $(node -p "require('./package.json').distro")
displayName: Merge distro
- script: |
npx https://aka.ms/enablesecurefeed standAlone
displayName: Switch to Terrapin packages
timeoutInMinutes: 5
condition: and(succeeded(), eq(variables['ENABLE_TERRAPIN'], 'true'))
- script: |
mkdir -p .build
echo -n $(VSCODE_ARCH) > .build/arch
echo -n $ENABLE_TERRAPIN > .build/terrapin
displayName: Prepare yarn cache flags
- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1
inputs:
keyfile: ".build/arch, .build/terrapin, build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock"
targetfolder: "**/node_modules, !**/node_modules/**/node_modules"
vstsFeed: "npm-vscode"
- script: |
set -e
for i in {1..3}; do # try 3 times, for Terrapin
yarn --frozen-lockfile && break
if [ $i -eq 3 ]; then
echo "Yarn failed too many times" >&2
exit 1
fi
echo "Yarn failed $i, trying again..."
done
env:
ELECTRON_SKIP_BINARY_DOWNLOAD: 1
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
displayName: Install dependencies
condition: and(succeeded(), ne(variables['CacheRestored'], 'true'))
# Mixin must run before optimize, because the CSS loader will inline small SVGs
- script: |
set -e
node build/azure-pipelines/mixin
displayName: Mix in quality
- script: |
set -e
yarn gulp hygiene
yarn monaco-compile-check
yarn valid-layers-check
displayName: Run hygiene, monaco compile & valid layers checks
condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false'))
- script: |
set -
./build/azure-pipelines/common/extract-telemetry.sh
displayName: Extract Telemetry
condition: and(succeeded(), ne(variables['VSCODE_PUBLISH'], 'false'))
- script: |
set -e
AZURE_WEBVIEW_STORAGE_ACCESS_KEY="$(vscode-webview-storage-key)" \
./build/azure-pipelines/common/publish-webview.sh
displayName: Publish Webview
condition: and(succeeded(), ne(variables['VSCODE_PUBLISH'], 'false'))

View file

@ -42,21 +42,28 @@ steps:
displayName: Merge distro
- script: |
npx https://aka.ms/enablesecurefeed standAlone
displayName: Switch to Terrapin packages
timeoutInMinutes: 5
condition: and(succeeded(), eq(variables['ENABLE_TERRAPIN'], 'true'))
mkdir -p .build
node build/azure-pipelines/common/computeNodeModulesCacheKey.js "web" $ENABLE_TERRAPIN > .build/yarnlockhash
displayName: Prepare yarn cache flags
- task: Cache@2
inputs:
key: 'nodeModules | $(Agent.OS) | .build/yarnlockhash'
path: .build/node_modules_cache
cacheHitVar: NODE_MODULES_RESTORED
displayName: Restore node_modules cache
- script: |
echo -n "web" > .build/arch
echo -n $ENABLE_TERRAPIN > .build/terrapin
displayName: Prepare yarn cache flag
set -e
tar -xzf .build/node_modules_cache/cache.tgz
condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true'))
displayName: Extract node_modules cache
- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1
inputs:
keyfile: ".build/arch, .build/terrapin, build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock"
targetfolder: "**/node_modules, !**/node_modules/**/node_modules"
vstsFeed: "npm-vscode"
- script: |
npx https://aka.ms/enablesecurefeed standAlone
timeoutInMinutes: 5
condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), eq(variables['ENABLE_TERRAPIN'], 'true'))
displayName: Switch to Terrapin packages
- script: |
set -e
@ -72,14 +79,15 @@ steps:
ELECTRON_SKIP_BINARY_DOWNLOAD: 1
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
displayName: Install dependencies
condition: and(succeeded(), ne(variables['CacheRestored'], 'true'))
condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'))
- task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1
inputs:
keyfile: ".build/arch, .build/terrapin, build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock"
targetfolder: "**/node_modules, !**/node_modules/**/node_modules"
vstsFeed: "npm-vscode"
condition: and(succeeded(), ne(variables['CacheRestored'], 'true'))
- script: |
set -e
node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt
mkdir -p .build/node_modules_cache
tar -czf .build/node_modules_cache/cache.tgz --files-from .build/node_modules_list.txt
condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'))
displayName: Create node_modules archive
- script: |
set -e

View file

@ -12,9 +12,14 @@ steps:
versionSpec: "2.x"
addToPath: true
- powershell: |
mkdir -Force .build
node build/azure-pipelines/common/computeNodeModulesCacheKey.js > .build/yarnlockhash
displayName: Prepare yarn cache flags
- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1
inputs:
keyfile: ".yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock"
keyfile: ".build/yarnlockhash"
targetfolder: "**/node_modules, !**/node_modules/**/node_modules"
vstsFeed: "vscode-build-cache"
@ -27,7 +32,7 @@ steps:
- task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1
inputs:
keyfile: ".yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock"
keyfile: ".build/yarnlockhash"
targetfolder: "**/node_modules, !**/node_modules/**/node_modules"
vstsFeed: "vscode-build-cache"
condition: and(succeeded(), ne(variables['CacheRestored'], 'true'))

View file

@ -45,22 +45,31 @@ steps:
exec { git pull --no-rebase https://github.com/$(VSCODE_MIXIN_REPO).git $(node -p "require('./package.json').distro") }
displayName: Merge distro
- script: |
npx https://aka.ms/enablesecurefeed standAlone
displayName: Switch to Terrapin packages
timeoutInMinutes: 7
condition: and(succeeded(), eq(variables['ENABLE_TERRAPIN'], 'true'))
- powershell: |
"$(VSCODE_ARCH)" | Out-File -Encoding ascii -NoNewLine .build\arch
"$env:ENABLE_TERRAPIN" | Out-File -Encoding ascii -NoNewLine .build\terrapin
node build/azure-pipelines/common/computeNodeModulesCacheKey.js > .build/yarnlockhash
displayName: Prepare yarn cache flags
- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1
- task: Cache@2
inputs:
keyfile: ".build/arch, .build/terrapin, build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock"
targetfolder: "**/node_modules, !**/node_modules/**/node_modules"
vstsFeed: "npm-vscode"
key: 'nodeModules | $(Agent.OS) | .build/arch, .build/terrapin, .build/yarnlockhash'
path: .build/node_modules_cache
cacheHitVar: NODE_MODULES_RESTORED
displayName: Restore node_modules cache
- powershell: |
. build/azure-pipelines/win32/exec.ps1
$ErrorActionPreference = "Stop"
exec { 7z.exe x .build/node_modules_cache/cache.7z -aos }
condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true'))
displayName: Extract node_modules cache
- script: |
npx https://aka.ms/enablesecurefeed standAlone
timeoutInMinutes: 5
condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), eq(variables['ENABLE_TERRAPIN'], 'true'))
displayName: Switch to Terrapin packages
- powershell: |
. build/azure-pipelines/win32/exec.ps1
@ -73,14 +82,16 @@ steps:
ELECTRON_SKIP_BINARY_DOWNLOAD: 1
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
displayName: Install dependencies
condition: and(succeeded(), ne(variables['CacheRestored'], 'true'))
condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'))
- task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1
inputs:
keyfile: ".build/arch, .build/terrapin, build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock"
targetfolder: "**/node_modules, !**/node_modules/**/node_modules"
vstsFeed: "npm-vscode"
condition: and(succeeded(), ne(variables['CacheRestored'], 'true'))
- powershell: |
. build/azure-pipelines/win32/exec.ps1
$ErrorActionPreference = "Stop"
exec { node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt }
exec { mkdir -Force .build/node_modules_cache }
exec { 7z.exe a .build/node_modules_cache/cache.7z -mx3 `@.build/node_modules_list.txt }
condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'))
displayName: Create node_modules archive
- powershell: |
. build/azure-pipelines/win32/exec.ps1
@ -144,7 +155,7 @@ steps:
$ErrorActionPreference = "Stop"
exec { yarn --cwd test/integration/browser compile }
displayName: Compile integration tests
condition: and(succeeded(), eq(variables['CacheRestored'], 'true'), eq(variables['VSCODE_STEP_ON_IT'], 'false'), ne(variables['VSCODE_ARCH'], 'arm64'))
condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true'), eq(variables['VSCODE_STEP_ON_IT'], 'false'), ne(variables['VSCODE_ARCH'], 'arm64'))
- powershell: |
# Figure out the full absolute path of the product we just built

36
build/eslint.js Normal file
View file

@ -0,0 +1,36 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
const es = require('event-stream');
const vfs = require('vinyl-fs');
const { jsHygieneFilter, tsHygieneFilter } = require('./filters');
function eslint() {
const gulpeslint = require('gulp-eslint');
return vfs
.src([...jsHygieneFilter, ...tsHygieneFilter], { base: '.', follow: true, allowEmpty: true })
.pipe(
gulpeslint({
configFile: '.eslintrc.json',
rulePaths: ['./build/lib/eslint'],
})
)
.pipe(gulpeslint.formatEach('compact'))
.pipe(
gulpeslint.results((results) => {
if (results.warningCount > 0 || results.errorCount > 0) {
throw new Error('eslint failed with warnings and/or errors');
}
})
).pipe(es.through(function () { /* noop, important for the stream to end */ }));
}
if (require.main === module) {
eslint().on('error', (err) => {
console.error();
console.error(err);
process.exit(1);
});
}

151
build/filters.js Normal file
View file

@ -0,0 +1,151 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/**
* Hygiene works by creating cascading subsets of all our files and
* passing them through a sequence of checks. Here are the current subsets,
* named according to the checks performed on them. Each subset contains
* the following one, as described in mathematical notation:
*
* all eol indentation copyright typescript
*/
module.exports.all = [
'*',
'build/**/*',
'extensions/**/*',
'scripts/**/*',
'src/**/*',
'test/**/*',
'!out*/**',
'!test/**/out/**',
'!**/node_modules/**',
];
module.exports.indentationFilter = [
'**',
// except specific files
'!**/ThirdPartyNotices.txt',
'!**/LICENSE.{txt,rtf}',
'!LICENSES.chromium.html',
'!**/LICENSE',
'!src/vs/nls.js',
'!src/vs/nls.build.js',
'!src/vs/css.js',
'!src/vs/css.build.js',
'!src/vs/loader.js',
'!src/vs/base/common/insane/insane.js',
'!src/vs/base/common/marked/marked.js',
'!src/vs/base/common/semver/semver.js',
'!src/vs/base/node/terminateProcess.sh',
'!src/vs/base/node/cpuUsage.sh',
'!test/unit/assert.js',
'!resources/linux/snap/electron-launch',
// except specific folders
'!test/automation/out/**',
'!test/monaco/out/**',
'!test/smoke/out/**',
'!extensions/typescript-language-features/test-workspace/**',
'!extensions/vscode-api-tests/testWorkspace/**',
'!extensions/vscode-api-tests/testWorkspace2/**',
'!build/monaco/**',
'!build/win32/**',
// except multiple specific files
'!**/package.json',
'!**/yarn.lock',
'!**/yarn-error.log',
// except multiple specific folders
'!**/codicon/**',
'!**/fixtures/**',
'!**/lib/**',
'!extensions/**/dist/**',
'!extensions/**/out/**',
'!extensions/**/snippets/**',
'!extensions/**/syntaxes/**',
'!extensions/**/themes/**',
'!extensions/**/colorize-fixtures/**',
// except specific file types
'!src/vs/*/**/*.d.ts',
'!src/typings/**/*.d.ts',
'!extensions/**/*.d.ts',
'!**/*.{svg,exe,png,bmp,jpg,scpt,bat,cmd,cur,ttf,woff,eot,md,ps1,template,yaml,yml,d.ts.recipe,ico,icns,plist}',
'!build/{lib,download,darwin}/**/*.js',
'!build/**/*.sh',
'!build/azure-pipelines/**/*.js',
'!build/azure-pipelines/**/*.config',
'!**/Dockerfile',
'!**/Dockerfile.*',
'!**/*.Dockerfile',
'!**/*.dockerfile',
'!extensions/markdown-language-features/media/*.js',
'!extensions/simple-browser/media/*.js',
];
module.exports.copyrightFilter = [
'**',
'!**/*.desktop',
'!**/*.json',
'!**/*.html',
'!**/*.template',
'!**/*.md',
'!**/*.bat',
'!**/*.cmd',
'!**/*.ico',
'!**/*.icns',
'!**/*.xml',
'!**/*.sh',
'!**/*.txt',
'!**/*.xpm',
'!**/*.opts',
'!**/*.disabled',
'!**/*.code-workspace',
'!**/*.js.map',
'!build/**/*.init',
'!resources/linux/snap/snapcraft.yaml',
'!resources/win32/bin/code.js',
'!resources/web/code-web.js',
'!resources/completions/**',
'!extensions/configuration-editing/build/inline-allOf.ts',
'!extensions/markdown-language-features/media/highlight.css',
'!extensions/html-language-features/server/src/modes/typescript/*',
'!extensions/*/server/bin/*',
'!src/vs/editor/test/node/classification/typescript-test.ts',
];
module.exports.jsHygieneFilter = [
'src/**/*.js',
'build/gulpfile.*.js',
'!src/vs/loader.js',
'!src/vs/css.js',
'!src/vs/nls.js',
'!src/vs/css.build.js',
'!src/vs/nls.build.js',
'!src/**/insane.js',
'!src/**/marked.js',
'!src/**/semver.js',
'!**/test/**',
];
module.exports.tsHygieneFilter = [
'src/**/*.ts',
'test/**/*.ts',
'extensions/**/*.ts',
'!src/vs/*/**/*.d.ts',
'!src/typings/**/*.d.ts',
'!extensions/**/*.d.ts',
'!**/fixtures/**',
'!**/typings/**',
'!**/node_modules/**',
'!extensions/typescript-basics/test/colorize-fixtures/**',
'!extensions/vscode-api-tests/testWorkspace/**',
'!extensions/vscode-api-tests/testWorkspace2/**',
'!extensions/**/*.test.ts',
'!extensions/html-language-features/server/lib/jquery.d.ts',
];

View file

@ -11,6 +11,11 @@ const task = require('./lib/task');
const compilation = require('./lib/compilation');
// Full compile, including nls and inline sources in sourcemaps, for build
const compileBuildTask = task.define('compile-build', task.series(util.rimraf('out-build'), compilation.compileTask('src', 'out-build', true)));
const compileBuildTask = task.define('compile-build',
task.series(
util.rimraf('out-build'),
compilation.compileTask('src', 'out-build', true)
)
);
gulp.task(compileBuildTask);
exports.compileBuildTask = compileBuildTask;
exports.compileBuildTask = compileBuildTask;

View file

@ -16,8 +16,6 @@ const cp = require('child_process');
const compilation = require('./lib/compilation');
const monacoapi = require('./lib/monaco-api');
const fs = require('fs');
const webpack = require('webpack');
const webpackGulp = require('webpack-stream');
let root = path.dirname(__dirname);
let sha1 = util.getVersion(root);
@ -369,6 +367,9 @@ gulp.task('editor-distro',
);
const bundleEditorESMTask = task.define('editor-esm-bundle-webpack', () => {
const webpack = require('webpack');
const webpackGulp = require('webpack-stream');
const result = es.through();
const webpackConfigPath = path.join(root, 'build/monaco/monaco.webpack.config.js');

View file

@ -9,17 +9,13 @@ require('events').EventEmitter.defaultMaxListeners = 100;
const gulp = require('gulp');
const path = require('path');
const nodeUtil = require('util');
const tsb = require('gulp-tsb');
const es = require('event-stream');
const filter = require('gulp-filter');
const webpack = require('webpack');
const util = require('./lib/util');
const task = require('./lib/task');
const watcher = require('./lib/watch');
const createReporter = require('./lib/reporter').createReporter;
const glob = require('glob');
const sourcemaps = require('gulp-sourcemaps');
const nlsDev = require('vscode-nls-dev');
const root = path.dirname(__dirname);
const commit = util.getVersion(root);
const plumber = require('gulp-plumber');
@ -29,10 +25,50 @@ const ext = require('./lib/extensions');
const extensionsPath = path.join(path.dirname(__dirname), 'extensions');
const compilations = glob.sync('**/tsconfig.json', {
cwd: extensionsPath,
ignore: ['**/out/**', '**/node_modules/**']
});
// To save 250ms for each gulp startup, we are caching the result here
// const compilations = glob.sync('**/tsconfig.json', {
// cwd: extensionsPath,
// ignore: ['**/out/**', '**/node_modules/**']
// });
const compilations = [
'configuration-editing/build/tsconfig.json',
'configuration-editing/tsconfig.json',
'css-language-features/client/tsconfig.json',
'css-language-features/server/tsconfig.json',
'debug-auto-launch/tsconfig.json',
'debug-server-ready/tsconfig.json',
'emmet/tsconfig.json',
'extension-editing/tsconfig.json',
'git-ui/tsconfig.json',
'git/tsconfig.json',
'github-authentication/tsconfig.json',
'github/tsconfig.json',
'grunt/tsconfig.json',
'gulp/tsconfig.json',
'html-language-features/client/tsconfig.json',
'html-language-features/server/tsconfig.json',
'image-preview/tsconfig.json',
'jake/tsconfig.json',
'json-language-features/client/tsconfig.json',
'json-language-features/server/tsconfig.json',
'markdown-language-features/preview-src/tsconfig.json',
'markdown-language-features/tsconfig.json',
'merge-conflict/tsconfig.json',
'microsoft-authentication/tsconfig.json',
'npm/tsconfig.json',
'php-language-features/tsconfig.json',
'python/tsconfig.json',
'search-result/tsconfig.json',
'simple-browser/tsconfig.json',
'testing-editor-contributions/tsconfig.json',
'typescript-language-features/test-workspace/tsconfig.json',
'typescript-language-features/tsconfig.json',
'vscode-api-tests/tsconfig.json',
'vscode-colorize-tests/tsconfig.json',
'vscode-custom-editor-tests/tsconfig.json',
'vscode-notebook-tests/tsconfig.json',
'vscode-test-resolver/tsconfig.json'
];
const getBaseUrl = out => `https://ticino.blob.core.windows.net/sourcemaps/${commit}/${out}`;
@ -64,6 +100,10 @@ const tasks = compilations.map(function (tsconfigFile) {
}
function createPipeline(build, emitError) {
const nlsDev = require('vscode-nls-dev');
const tsb = require('gulp-tsb');
const sourcemaps = require('gulp-sourcemaps');
const reporter = createReporter('extensions');
overrideOptions.inlineSources = Boolean(build);
@ -170,6 +210,8 @@ const compileExtensionsBuildTask = task.define('compile-extensions-build', task.
));
gulp.task(compileExtensionsBuildTask);
gulp.task(task.define('extensions-ci', task.series(compileExtensionsBuildTask)));
exports.compileExtensionsBuildTask = compileExtensionsBuildTask;
const compileWebExtensionsTask = task.define('compile-web', () => buildWebExtensions(false));
@ -181,6 +223,7 @@ gulp.task(watchWebExtensionsTask);
exports.watchWebExtensionsTask = watchWebExtensionsTask;
async function buildWebExtensions(isWatch) {
const webpack = require('webpack');
const webpackConfigLocations = await nodeUtil.promisify(glob)(
path.join(extensionsPath, '**', 'extension-browser.webpack.config.js'),

View file

@ -4,51 +4,28 @@
*--------------------------------------------------------------------------------------------*/
const gulp = require('gulp');
const filter = require('gulp-filter');
const es = require('event-stream');
const gulpeslint = require('gulp-eslint');
const vfs = require('vinyl-fs');
const path = require('path');
const task = require('./lib/task');
const { all, jsHygieneFilter, tsHygieneFilter, hygiene } = require('./hygiene');
gulp.task('eslint', () => {
return vfs
.src(all, { base: '.', follow: true, allowEmpty: true })
.pipe(filter(jsHygieneFilter.concat(tsHygieneFilter)))
.pipe(
gulpeslint({
configFile: '.eslintrc.json',
rulePaths: ['./build/lib/eslint'],
})
)
.pipe(gulpeslint.formatEach('compact'))
.pipe(
gulpeslint.results((results) => {
if (results.warningCount > 0 || results.errorCount > 0) {
throw new Error('eslint failed with warnings and/or errors');
}
})
);
});
const { hygiene } = require('./hygiene');
function checkPackageJSON(actualPath) {
const actual = require(path.join(__dirname, '..', actualPath));
const rootPackageJSON = require('../package.json');
const checkIncluded = (set1, set2) => {
for (let depName in set1) {
const depVersion = set1[depName];
const rootDepVersion = set2[depName];
if (!rootDepVersion) {
// missing in root is allowed
continue;
}
if (depVersion !== rootDepVersion) {
this.emit(
'error',
`The dependency ${depName} in '${actualPath}' (${depVersion}) is different than in the root package.json (${rootDepVersion})`
);
}
const depVersion = set1[depName];
const rootDepVersion = set2[depName];
if (!rootDepVersion) {
// missing in root is allowed
continue;
}
if (depVersion !== rootDepVersion) {
this.emit(
'error',
`The dependency ${depName} in '${actualPath}' (${depVersion}) is different than in the root package.json (${rootDepVersion})`
);
}
}
};
@ -67,7 +44,5 @@ const checkPackageJSONTask = task.define('check-package-json', () => {
});
gulp.task(checkPackageJSONTask);
gulp.task(
'hygiene',
task.series(checkPackageJSONTask, () => hygiene())
);
const hygieneTask = task.define('hygiene', task.series(checkPackageJSONTask, () => hygiene(undefined, false)));
gulp.task(hygieneTask);

View file

@ -14,10 +14,8 @@ const task = require('./lib/task');
const vfs = require('vinyl-fs');
const flatmap = require('gulp-flatmap');
const gunzip = require('gulp-gunzip');
const untar = require('gulp-untar');
const File = require('vinyl');
const fs = require('fs');
const remote = require('gulp-remote-retry-src');
const rename = require('gulp-rename');
const filter = require('gulp-filter');
const cp = require('child_process');
@ -77,6 +75,9 @@ if (defaultNodeTask) {
}
function nodejs(platform, arch) {
const remote = require('gulp-remote-retry-src');
const untar = require('gulp-untar');
if (arch === 'ia32') {
arch = 'x86';
}

View file

@ -11,13 +11,10 @@ const os = require('os');
const cp = require('child_process');
const path = require('path');
const es = require('event-stream');
const azure = require('gulp-azure-storage');
const electron = require('gulp-atom-electron');
const vfs = require('vinyl-fs');
const rename = require('gulp-rename');
const replace = require('gulp-replace');
const filter = require('gulp-filter');
const json = require('gulp-json-editor');
const _ = require('underscore');
const util = require('./lib/util');
const task = require('./lib/task');
@ -102,6 +99,16 @@ const minifyVSCodeTask = task.define('minify-vscode', task.series(
));
gulp.task(minifyVSCodeTask);
const core = task.define('core-ci', task.series(
gulp.task('compile-build'),
task.parallel(
gulp.task('minify-vscode'),
gulp.task('minify-vscode-reh'),
gulp.task('minify-vscode-reh-web'),
)
));
gulp.task(core);
/**
* Compute checksums for some files.
*
@ -143,6 +150,9 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op
platform = platform || process.platform;
return () => {
const electron = require('gulp-atom-electron');
const json = require('gulp-json-editor');
const out = sourceFolderName;
const checksums = computeChecksums(out, [
@ -503,6 +513,8 @@ gulp.task(task.define(
task.series(
generateVSCodeConfigurationTask,
() => {
const azure = require('gulp-azure-storage');
if (!shouldSetupSettingsSearch()) {
const branch = process.env.BUILD_SOURCEBRANCH;
console.log(`Only runs on master and release branches, not ${branch}`);

View file

@ -5,157 +5,12 @@
const filter = require('gulp-filter');
const es = require('event-stream');
const gulpeslint = require('gulp-eslint');
const tsfmt = require('typescript-formatter');
const VinylFile = require('vinyl');
const vfs = require('vinyl-fs');
const path = require('path');
const fs = require('fs');
const pall = require('p-all');
/**
* Hygiene works by creating cascading subsets of all our files and
* passing them through a sequence of checks. Here are the current subsets,
* named according to the checks performed on them. Each subset contains
* the following one, as described in mathematical notation:
*
* all eol indentation copyright typescript
*/
const all = [
'*',
'build/**/*',
'extensions/**/*',
'scripts/**/*',
'src/**/*',
'test/**/*',
'!test/**/out/**',
'!**/node_modules/**',
];
module.exports.all = all;
const indentationFilter = [
'**',
// except specific files
'!**/ThirdPartyNotices.txt',
'!**/LICENSE.{txt,rtf}',
'!LICENSES.chromium.html',
'!**/LICENSE',
'!src/vs/nls.js',
'!src/vs/nls.build.js',
'!src/vs/css.js',
'!src/vs/css.build.js',
'!src/vs/loader.js',
'!src/vs/base/common/insane/insane.js',
'!src/vs/base/common/marked/marked.js',
'!src/vs/base/common/semver/semver.js',
'!src/vs/base/node/terminateProcess.sh',
'!src/vs/base/node/cpuUsage.sh',
'!test/unit/assert.js',
'!resources/linux/snap/electron-launch',
// except specific folders
'!test/automation/out/**',
'!test/smoke/out/**',
'!extensions/typescript-language-features/test-workspace/**',
'!extensions/vscode-api-tests/testWorkspace/**',
'!extensions/vscode-api-tests/testWorkspace2/**',
'!build/monaco/**',
'!build/win32/**',
// except multiple specific files
'!**/package.json',
'!**/yarn.lock',
'!**/yarn-error.log',
// except multiple specific folders
'!**/codicon/**',
'!**/fixtures/**',
'!**/lib/**',
'!extensions/**/dist/**',
'!extensions/**/out/**',
'!extensions/**/snippets/**',
'!extensions/**/syntaxes/**',
'!extensions/**/themes/**',
'!extensions/**/colorize-fixtures/**',
// except specific file types
'!src/vs/*/**/*.d.ts',
'!src/typings/**/*.d.ts',
'!extensions/**/*.d.ts',
'!**/*.{svg,exe,png,bmp,jpg,scpt,bat,cmd,cur,ttf,woff,eot,md,ps1,template,yaml,yml,d.ts.recipe,ico,icns,plist}',
'!build/{lib,download,darwin}/**/*.js',
'!build/**/*.sh',
'!build/azure-pipelines/**/*.js',
'!build/azure-pipelines/**/*.config',
'!**/Dockerfile',
'!**/Dockerfile.*',
'!**/*.Dockerfile',
'!**/*.dockerfile',
'!extensions/markdown-language-features/media/*.js',
];
const copyrightFilter = [
'**',
'!**/*.desktop',
'!**/*.json',
'!**/*.html',
'!**/*.template',
'!**/*.md',
'!**/*.bat',
'!**/*.cmd',
'!**/*.ico',
'!**/*.icns',
'!**/*.xml',
'!**/*.sh',
'!**/*.txt',
'!**/*.xpm',
'!**/*.opts',
'!**/*.disabled',
'!**/*.code-workspace',
'!**/*.js.map',
'!build/**/*.init',
'!resources/linux/snap/snapcraft.yaml',
'!resources/win32/bin/code.js',
'!resources/web/code-web.js',
'!resources/completions/**',
'!extensions/configuration-editing/build/inline-allOf.ts',
'!extensions/markdown-language-features/media/highlight.css',
'!extensions/html-language-features/server/src/modes/typescript/*',
'!extensions/*/server/bin/*',
'!src/vs/editor/test/node/classification/typescript-test.ts',
];
const jsHygieneFilter = [
'src/**/*.js',
'build/gulpfile.*.js',
'!src/vs/loader.js',
'!src/vs/css.js',
'!src/vs/nls.js',
'!src/vs/css.build.js',
'!src/vs/nls.build.js',
'!src/**/insane.js',
'!src/**/marked.js',
'!src/**/semver.js',
'!**/test/**',
];
module.exports.jsHygieneFilter = jsHygieneFilter;
const tsHygieneFilter = [
'src/**/*.ts',
'test/**/*.ts',
'extensions/**/*.ts',
'!**/fixtures/**',
'!**/typings/**',
'!**/node_modules/**',
'!extensions/typescript-basics/test/colorize-fixtures/**',
'!extensions/vscode-api-tests/testWorkspace/**',
'!extensions/vscode-api-tests/testWorkspace2/**',
'!extensions/**/*.test.ts',
'!extensions/html-language-features/server/lib/jquery.d.ts',
];
module.exports.tsHygieneFilter = tsHygieneFilter;
const { all, copyrightFilter, indentationFilter, jsHygieneFilter, tsHygieneFilter } = require('./filters');
const copyrightHeaderLines = [
'/*---------------------------------------------------------------------------------------------',
@ -164,7 +19,10 @@ const copyrightHeaderLines = [
' *--------------------------------------------------------------------------------------------*/',
];
function hygiene(some) {
function hygiene(some, linting = true) {
const gulpeslint = require('gulp-eslint');
const tsfmt = require('typescript-formatter');
let errorCount = 0;
const productJson = es.through(function (file) {
@ -274,26 +132,32 @@ function hygiene(some) {
.pipe(filter(copyrightFilter))
.pipe(copyrights);
const typescript = result.pipe(filter(tsHygieneFilter)).pipe(formatting);
const streams = [
result.pipe(filter(tsHygieneFilter)).pipe(formatting)
];
const javascript = result
.pipe(filter(jsHygieneFilter.concat(tsHygieneFilter)))
.pipe(
gulpeslint({
configFile: '.eslintrc.json',
rulePaths: ['./build/lib/eslint'],
})
)
.pipe(gulpeslint.formatEach('compact'))
.pipe(
gulpeslint.results((results) => {
errorCount += results.warningCount;
errorCount += results.errorCount;
})
if (linting) {
streams.push(
result
.pipe(filter([...jsHygieneFilter, ...tsHygieneFilter]))
.pipe(
gulpeslint({
configFile: '.eslintrc.json',
rulePaths: ['./build/lib/eslint'],
})
)
.pipe(gulpeslint.formatEach('compact'))
.pipe(
gulpeslint.results((results) => {
errorCount += results.warningCount;
errorCount += results.errorCount;
})
)
);
}
let count = 0;
return es.merge(typescript, javascript).pipe(
return es.merge(...streams).pipe(
es.through(
function (data) {
count++;

View file

@ -8,9 +8,6 @@ exports.watchTask = exports.compileTask = void 0;
const es = require("event-stream");
const fs = require("fs");
const gulp = require("gulp");
const bom = require("gulp-bom");
const sourcemaps = require("gulp-sourcemaps");
const tsb = require("gulp-tsb");
const path = require("path");
const monacodts = require("./monaco-api");
const nls = require("./nls");
@ -36,10 +33,13 @@ function getTypeScriptCompilerOptions(src) {
return options;
}
function createCompile(src, build, emitError) {
const tsb = require('gulp-tsb');
const sourcemaps = require('gulp-sourcemaps');
const projectPath = path.join(__dirname, '../../', src, 'tsconfig.json');
const overrideOptions = Object.assign(Object.assign({}, getTypeScriptCompilerOptions(src)), { inlineSources: Boolean(build) });
const compilation = tsb.create(projectPath, overrideOptions, false, err => reporter(err));
function pipeline(token) {
const bom = require('gulp-bom');
const utf8Filter = util.filter(data => /(\/|\\)test(\/|\\).*utf8/.test(data.path));
const tsFilter = util.filter(data => /\.ts$/.test(data.path));
const noDeclarationsFilter = util.filter(data => !(/\.d\.ts$/.test(data.path)));
@ -52,7 +52,7 @@ function createCompile(src, build, emitError) {
.pipe(util.loadSourcemaps())
.pipe(compilation(token))
.pipe(noDeclarationsFilter)
.pipe(build ? nls() : es.through())
.pipe(build ? nls.nls() : es.through())
.pipe(noDeclarationsFilter.restore)
.pipe(sourcemaps.write('.', {
addComment: false,

View file

@ -8,9 +8,6 @@
import * as es from 'event-stream';
import * as fs from 'fs';
import * as gulp from 'gulp';
import * as bom from 'gulp-bom';
import * as sourcemaps from 'gulp-sourcemaps';
import * as tsb from 'gulp-tsb';
import * as path from 'path';
import * as monacodts from './monaco-api';
import * as nls from './nls';
@ -41,12 +38,17 @@ function getTypeScriptCompilerOptions(src: string): ts.CompilerOptions {
}
function createCompile(src: string, build: boolean, emitError?: boolean) {
const tsb = require('gulp-tsb') as typeof import('gulp-tsb');
const sourcemaps = require('gulp-sourcemaps') as typeof import('gulp-sourcemaps');
const projectPath = path.join(__dirname, '../../', src, 'tsconfig.json');
const overrideOptions = { ...getTypeScriptCompilerOptions(src), inlineSources: Boolean(build) };
const compilation = tsb.create(projectPath, overrideOptions, false, err => reporter(err));
function pipeline(token?: util.ICancellationToken) {
const bom = require('gulp-bom') as typeof import('gulp-bom');
const utf8Filter = util.filter(data => /(\/|\\)test(\/|\\).*utf8/.test(data.path));
const tsFilter = util.filter(data => /\.ts$/.test(data.path));
@ -61,7 +63,7 @@ function createCompile(src: string, build: boolean, emitError?: boolean) {
.pipe(util.loadSourcemaps())
.pipe(compilation(token))
.pipe(noDeclarationsFilter)
.pipe(build ? nls() : es.through())
.pipe(build ? nls.nls() : es.through())
.pipe(noDeclarationsFilter.restore)
.pipe(sourcemaps.write('.', {
addComment: false,

View file

@ -9,10 +9,8 @@ const fs = require("fs");
const path = require("path");
const vfs = require("vinyl-fs");
const filter = require("gulp-filter");
const json = require("gulp-json-editor");
const _ = require("underscore");
const util = require("./util");
const electron = require('gulp-atom-electron');
const root = path.dirname(path.dirname(__dirname));
const product = JSON.parse(fs.readFileSync(path.join(root, 'product.json'), 'utf8'));
const commit = util.getVersion(root);
@ -80,6 +78,8 @@ exports.config = {
};
function getElectron(arch) {
return () => {
const electron = require('gulp-atom-electron');
const json = require('gulp-json-editor');
const electronOpts = _.extend({}, exports.config, {
platform: process.platform,
arch: arch === 'armhf' ? 'arm' : arch,

View file

@ -9,12 +9,9 @@ import * as fs from 'fs';
import * as path from 'path';
import * as vfs from 'vinyl-fs';
import * as filter from 'gulp-filter';
import * as json from 'gulp-json-editor';
import * as _ from 'underscore';
import * as util from './util';
const electron = require('gulp-atom-electron');
const root = path.dirname(path.dirname(__dirname));
const product = JSON.parse(fs.readFileSync(path.join(root, 'product.json'), 'utf8'));
const commit = util.getVersion(root);
@ -86,6 +83,9 @@ export const config = {
function getElectron(arch: string): () => NodeJS.ReadWriteStream {
return () => {
const electron = require('gulp-atom-electron');
const json = require('gulp-json-editor') as typeof import('gulp-json-editor');
const electronOpts = _.extend({}, config, {
platform: process.platform,
arch: arch === 'armhf' ? 'arm' : arch,

View file

@ -0,0 +1,33 @@
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
const experimental_utils_1 = require("@typescript-eslint/experimental-utils");
module.exports = new class ApiProviderNaming {
constructor() {
this.meta = {
messages: {
noToken: 'Function lacks a cancellation token, preferable as last argument',
}
};
}
create(context) {
return {
['TSInterfaceDeclaration[id.name=/.+Provider/] TSMethodSignature[key.name=/^(provide|resolve).+/]']: (node) => {
let found = false;
for (let param of node.params) {
if (param.type === experimental_utils_1.AST_NODE_TYPES.Identifier) {
found = found || param.name === 'token';
}
}
if (!found) {
context.report({
node,
messageId: 'noToken'
});
}
}
};
}
};

View file

@ -0,0 +1,38 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as eslint from 'eslint';
import { AST_NODE_TYPES, TSESTree } from '@typescript-eslint/experimental-utils';
export = new class ApiProviderNaming implements eslint.Rule.RuleModule {
readonly meta: eslint.Rule.RuleMetaData = {
messages: {
noToken: 'Function lacks a cancellation token, preferable as last argument',
}
};
create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener {
return {
['TSInterfaceDeclaration[id.name=/.+Provider/] TSMethodSignature[key.name=/^(provide|resolve).+/]']: (node: any) => {
let found = false;
for (let param of (<TSESTree.TSMethodSignature>node).params) {
if (param.type === AST_NODE_TYPES.Identifier) {
found = found || param.name === 'token';
}
}
if (!found) {
context.report({
node,
messageId: 'noToken'
});
}
}
};
}
};

View file

@ -0,0 +1,38 @@
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var _a;
module.exports = new (_a = class ApiProviderNaming {
constructor() {
this.meta = {
messages: {
naming: 'A provider should only have functions like provideXYZ or resolveXYZ',
}
};
}
create(context) {
const config = context.options[0];
const allowed = new Set(config.allowed);
return {
['TSInterfaceDeclaration[id.name=/.+Provider/] TSMethodSignature']: (node) => {
var _a;
const interfaceName = ((_a = node.parent) === null || _a === void 0 ? void 0 : _a.parent).id.name;
if (allowed.has(interfaceName)) {
// allowed
return;
}
const methodName = node.key.name;
if (!ApiProviderNaming._providerFunctionNames.test(methodName)) {
context.report({
node,
messageId: 'naming'
});
}
}
};
}
},
_a._providerFunctionNames = /^(provide|resolve|prepare).+/,
_a);

View file

@ -0,0 +1,45 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as eslint from 'eslint';
import { TSESTree } from '@typescript-eslint/experimental-utils';
export = new class ApiProviderNaming implements eslint.Rule.RuleModule {
readonly meta: eslint.Rule.RuleMetaData = {
messages: {
naming: 'A provider should only have functions like provideXYZ or resolveXYZ',
}
};
private static _providerFunctionNames = /^(provide|resolve|prepare).+/;
create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener {
const config = <{ allowed: string[] }>context.options[0];
const allowed = new Set(config.allowed);
return {
['TSInterfaceDeclaration[id.name=/.+Provider/] TSMethodSignature']: (node: any) => {
const interfaceName = (<TSESTree.TSInterfaceDeclaration>(<TSESTree.Identifier>node).parent?.parent).id.name;
if (allowed.has(interfaceName)) {
// allowed
return;
}
const methodName = (<any>(<TSESTree.TSMethodSignatureNonComputedName>node).key).name;
if (!ApiProviderNaming._providerFunctionNames.test(methodName)) {
context.report({
node,
messageId: 'naming'
});
}
}
};
}
};

View file

@ -11,20 +11,15 @@ const glob = require("glob");
const gulp = require("gulp");
const path = require("path");
const File = require("vinyl");
const vsce = require("vsce");
const stats_1 = require("./stats");
const util2 = require("./util");
const remote = require("gulp-remote-retry-src");
const vzip = require('gulp-vinyl-zip');
const filter = require("gulp-filter");
const rename = require("gulp-rename");
const fancyLog = require("fancy-log");
const ansiColors = require("ansi-colors");
const buffer = require('gulp-buffer');
const json = require("gulp-json-editor");
const jsoncParser = require("jsonc-parser");
const webpack = require('webpack');
const webpackGulp = require('webpack-stream');
const util = require('./util');
const root = path.dirname(path.dirname(__dirname));
const commit = util.getVersion(root);
@ -88,6 +83,9 @@ function fromLocalWebpack(extensionPath, webpackConfigFileName) {
}
}
}
const vsce = require('vsce');
const webpack = require('webpack');
const webpackGulp = require('webpack-stream');
vsce.listFiles({ cwd: extensionPath, packageManager: vsce.PackageManager.Yarn, packagedDependencies }).then(fileNames => {
const files = fileNames
.map(fileName => path.join(extensionPath, fileName))
@ -149,6 +147,7 @@ function fromLocalWebpack(extensionPath, webpackConfigFileName) {
}
function fromLocalNormal(extensionPath) {
const result = es.through();
const vsce = require('vsce');
vsce.listFiles({ cwd: extensionPath, packageManager: vsce.PackageManager.Yarn })
.then(fileNames => {
const files = fileNames
@ -170,6 +169,8 @@ const baseHeaders = {
'X-Market-User-Id': '291C1CD0-051A-4123-9B4B-30D60EF52EE2',
};
function fromMarketplace(extensionName, version, metadata) {
const remote = require('gulp-remote-retry-src');
const json = require('gulp-json-editor');
const [publisher, name] = extensionName.split('.');
const url = `https://marketplace.visualstudio.com/_apis/public/gallery/publishers/${publisher}/vsextensions/${name}/${version}/vspackage`;
fancyLog('Downloading extension:', ansiColors.yellow(`${extensionName}@${version}`), '...');

View file

@ -10,20 +10,15 @@ import * as gulp from 'gulp';
import * as path from 'path';
import { Stream } from 'stream';
import * as File from 'vinyl';
import * as vsce from 'vsce';
import { createStatsStream } from './stats';
import * as util2 from './util';
import remote = require('gulp-remote-retry-src');
const vzip = require('gulp-vinyl-zip');
import filter = require('gulp-filter');
import rename = require('gulp-rename');
import * as fancyLog from 'fancy-log';
import * as ansiColors from 'ansi-colors';
const buffer = require('gulp-buffer');
import json = require('gulp-json-editor');
import * as jsoncParser from 'jsonc-parser';
const webpack = require('webpack');
const webpackGulp = require('webpack-stream');
const util = require('./util');
const root = path.dirname(path.dirname(__dirname));
const commit = util.getVersion(root);
@ -97,6 +92,10 @@ function fromLocalWebpack(extensionPath: string, webpackConfigFileName: string):
}
}
const vsce = require('vsce') as typeof import('vsce');
const webpack = require('webpack');
const webpackGulp = require('webpack-stream');
vsce.listFiles({ cwd: extensionPath, packageManager: vsce.PackageManager.Yarn, packagedDependencies }).then(fileNames => {
const files = fileNames
.map(fileName => path.join(extensionPath, fileName))
@ -175,6 +174,8 @@ function fromLocalWebpack(extensionPath: string, webpackConfigFileName: string):
function fromLocalNormal(extensionPath: string): Stream {
const result = es.through();
const vsce = require('vsce') as typeof import('vsce');
vsce.listFiles({ cwd: extensionPath, packageManager: vsce.PackageManager.Yarn })
.then(fileNames => {
const files = fileNames
@ -200,6 +201,9 @@ const baseHeaders = {
};
export function fromMarketplace(extensionName: string, version: string, metadata: any): Stream {
const remote = require('gulp-remote-retry-src');
const json = require('gulp-json-editor') as typeof import('gulp-json-editor');
const [publisher, name] = extensionName.split('.');
const url = `https://marketplace.visualstudio.com/_apis/public/gallery/publishers/${publisher}/vsextensions/${name}/${version}/vspackage`;

View file

@ -6,7 +6,6 @@
Object.defineProperty(exports, "__esModule", { value: true });
exports.execute = exports.run3 = exports.DeclarationResolver = exports.FSProvider = exports.RECIPE_PATH = void 0;
const fs = require("fs");
const ts = require("typescript");
const path = require("path");
const fancyLog = require("fancy-log");
const ansiColors = require("ansi-colors");
@ -18,7 +17,7 @@ const DECLARATION_PATH = path.join(__dirname, '../../src/vs/monaco.d.ts');
function logErr(message, ...rest) {
fancyLog(ansiColors.yellow(`[monaco.d.ts]`), message, ...rest);
}
function isDeclaration(a) {
function isDeclaration(ts, a) {
return (a.kind === ts.SyntaxKind.InterfaceDeclaration
|| a.kind === ts.SyntaxKind.EnumDeclaration
|| a.kind === ts.SyntaxKind.ClassDeclaration
@ -26,7 +25,7 @@ function isDeclaration(a) {
|| a.kind === ts.SyntaxKind.FunctionDeclaration
|| a.kind === ts.SyntaxKind.ModuleDeclaration);
}
function visitTopLevelDeclarations(sourceFile, visitor) {
function visitTopLevelDeclarations(ts, sourceFile, visitor) {
let stop = false;
let visit = (node) => {
if (stop) {
@ -49,9 +48,9 @@ function visitTopLevelDeclarations(sourceFile, visitor) {
};
visit(sourceFile);
}
function getAllTopLevelDeclarations(sourceFile) {
function getAllTopLevelDeclarations(ts, sourceFile) {
let all = [];
visitTopLevelDeclarations(sourceFile, (node) => {
visitTopLevelDeclarations(ts, sourceFile, (node) => {
if (node.kind === ts.SyntaxKind.InterfaceDeclaration || node.kind === ts.SyntaxKind.ClassDeclaration || node.kind === ts.SyntaxKind.ModuleDeclaration) {
let interfaceDeclaration = node;
let triviaStart = interfaceDeclaration.pos;
@ -71,10 +70,10 @@ function getAllTopLevelDeclarations(sourceFile) {
});
return all;
}
function getTopLevelDeclaration(sourceFile, typeName) {
function getTopLevelDeclaration(ts, sourceFile, typeName) {
let result = null;
visitTopLevelDeclarations(sourceFile, (node) => {
if (isDeclaration(node) && node.name) {
visitTopLevelDeclarations(ts, sourceFile, (node) => {
if (isDeclaration(ts, node) && node.name) {
if (node.name.text === typeName) {
result = node;
return true /*stop*/;
@ -104,18 +103,18 @@ function hasModifier(modifiers, kind) {
}
return false;
}
function isStatic(member) {
function isStatic(ts, member) {
return hasModifier(member.modifiers, ts.SyntaxKind.StaticKeyword);
}
function isDefaultExport(declaration) {
function isDefaultExport(ts, declaration) {
return (hasModifier(declaration.modifiers, ts.SyntaxKind.DefaultKeyword)
&& hasModifier(declaration.modifiers, ts.SyntaxKind.ExportKeyword));
}
function getMassagedTopLevelDeclarationText(sourceFile, declaration, importName, usage, enums) {
function getMassagedTopLevelDeclarationText(ts, sourceFile, declaration, importName, usage, enums) {
let result = getNodeText(sourceFile, declaration);
if (declaration.kind === ts.SyntaxKind.InterfaceDeclaration || declaration.kind === ts.SyntaxKind.ClassDeclaration) {
let interfaceDeclaration = declaration;
const staticTypeName = (isDefaultExport(interfaceDeclaration)
const staticTypeName = (isDefaultExport(ts, interfaceDeclaration)
? `${importName}.default`
: `${importName}.${declaration.name.text}`);
let instanceTypeName = staticTypeName;
@ -137,7 +136,7 @@ function getMassagedTopLevelDeclarationText(sourceFile, declaration, importName,
else {
const memberName = member.name.text;
const memberAccess = (memberName.indexOf('.') >= 0 ? `['${memberName}']` : `.${memberName}`);
if (isStatic(member)) {
if (isStatic(ts, member)) {
usage.push(`a = ${staticTypeName}${memberAccess};`);
}
else {
@ -191,7 +190,7 @@ function getMassagedTopLevelDeclarationText(sourceFile, declaration, importName,
}
return result;
}
function format(text, endl) {
function format(ts, text, endl) {
const REALLY_FORMAT = false;
text = preformat(text, endl);
if (!REALLY_FORMAT) {
@ -336,7 +335,7 @@ function createReplacer(data) {
});
return createReplacerFromDirectives(directives);
}
function generateDeclarationFile(recipe, sourceFileGetter) {
function generateDeclarationFile(ts, recipe, sourceFileGetter) {
const endl = /\r\n/.test(recipe) ? '\r\n' : '\n';
let lines = recipe.split(endl);
let result = [];
@ -379,14 +378,14 @@ function generateDeclarationFile(recipe, sourceFileGetter) {
if (typeName.length === 0) {
return;
}
let declaration = getTopLevelDeclaration(sourceFile, typeName);
let declaration = getTopLevelDeclaration(ts, sourceFile, typeName);
if (!declaration) {
logErr(`While handling ${line}`);
logErr(`Cannot find ${typeName}`);
failed = true;
return;
}
result.push(replacer(getMassagedTopLevelDeclarationText(sourceFile, declaration, importName, usage, enums)));
result.push(replacer(getMassagedTopLevelDeclarationText(ts, sourceFile, declaration, importName, usage, enums)));
});
return;
}
@ -413,8 +412,8 @@ function generateDeclarationFile(recipe, sourceFileGetter) {
typesToExcludeMap[typeName] = true;
typesToExcludeArr.push(typeName);
});
getAllTopLevelDeclarations(sourceFile).forEach((declaration) => {
if (isDeclaration(declaration) && declaration.name) {
getAllTopLevelDeclarations(ts, sourceFile).forEach((declaration) => {
if (isDeclaration(ts, declaration) && declaration.name) {
if (typesToExcludeMap[declaration.name.text]) {
return;
}
@ -428,7 +427,7 @@ function generateDeclarationFile(recipe, sourceFileGetter) {
}
}
}
result.push(replacer(getMassagedTopLevelDeclarationText(sourceFile, declaration, importName, usage, enums)));
result.push(replacer(getMassagedTopLevelDeclarationText(ts, sourceFile, declaration, importName, usage, enums)));
});
return;
}
@ -450,7 +449,7 @@ function generateDeclarationFile(recipe, sourceFileGetter) {
resultTxt = resultTxt.replace(/\bURI\b/g, 'Uri');
resultTxt = resultTxt.replace(/\bEvent</g, 'IEvent<');
resultTxt = resultTxt.split(/\r\n|\n|\r/).join(endl);
resultTxt = format(resultTxt, endl);
resultTxt = format(ts, resultTxt, endl);
resultTxt = resultTxt.split(/\r\n|\n|\r/).join(endl);
enums.sort((e1, e2) => {
if (e1.enumName < e2.enumName) {
@ -471,7 +470,7 @@ function generateDeclarationFile(recipe, sourceFileGetter) {
''
].concat(enums.map(e => e.text)).join(endl);
resultEnums = resultEnums.split(/\r\n|\n|\r/).join(endl);
resultEnums = format(resultEnums, endl);
resultEnums = format(ts, resultEnums, endl);
resultEnums = resultEnums.split(/\r\n|\n|\r/).join(endl);
return {
result: resultTxt,
@ -479,9 +478,9 @@ function generateDeclarationFile(recipe, sourceFileGetter) {
enums: resultEnums
};
}
function _run(sourceFileGetter) {
function _run(ts, sourceFileGetter) {
const recipe = fs.readFileSync(exports.RECIPE_PATH).toString();
const t = generateDeclarationFile(recipe, sourceFileGetter);
const t = generateDeclarationFile(ts, recipe, sourceFileGetter);
if (!t) {
return null;
}
@ -521,6 +520,7 @@ class CacheEntry {
class DeclarationResolver {
constructor(_fsProvider) {
this._fsProvider = _fsProvider;
this.ts = require('typescript');
this._sourceFileCache = Object.create(null);
}
invalidateCache(moduleId) {
@ -555,25 +555,26 @@ class DeclarationResolver {
if (/\.d\.ts$/.test(moduleId)) {
// const mtime = this._fsProvider.statFileSync()
const fileContents = this._fsProvider.readFileSync(moduleId, fileName).toString();
return new CacheEntry(ts.createSourceFile(fileName, fileContents, ts.ScriptTarget.ES5), mtime);
return new CacheEntry(this.ts.createSourceFile(fileName, fileContents, this.ts.ScriptTarget.ES5), mtime);
}
const fileContents = this._fsProvider.readFileSync(moduleId, fileName).toString();
const fileMap = {
'file.ts': fileContents
};
const service = ts.createLanguageService(new TypeScriptLanguageServiceHost({}, fileMap, {}));
const service = this.ts.createLanguageService(new TypeScriptLanguageServiceHost(this.ts, {}, fileMap, {}));
const text = service.getEmitOutput('file.ts', true, true).outputFiles[0].text;
return new CacheEntry(ts.createSourceFile(fileName, text, ts.ScriptTarget.ES5), mtime);
return new CacheEntry(this.ts.createSourceFile(fileName, text, this.ts.ScriptTarget.ES5), mtime);
}
}
exports.DeclarationResolver = DeclarationResolver;
function run3(resolver) {
const sourceFileGetter = (moduleId) => resolver.getDeclarationSourceFile(moduleId);
return _run(sourceFileGetter);
return _run(resolver.ts, sourceFileGetter);
}
exports.run3 = run3;
class TypeScriptLanguageServiceHost {
constructor(libs, files, compilerOptions) {
constructor(ts, libs, files, compilerOptions) {
this._ts = ts;
this._libs = libs;
this._files = files;
this._compilerOptions = compilerOptions;
@ -595,17 +596,17 @@ class TypeScriptLanguageServiceHost {
}
getScriptSnapshot(fileName) {
if (this._files.hasOwnProperty(fileName)) {
return ts.ScriptSnapshot.fromString(this._files[fileName]);
return this._ts.ScriptSnapshot.fromString(this._files[fileName]);
}
else if (this._libs.hasOwnProperty(fileName)) {
return ts.ScriptSnapshot.fromString(this._libs[fileName]);
return this._ts.ScriptSnapshot.fromString(this._libs[fileName]);
}
else {
return ts.ScriptSnapshot.fromString('');
return this._ts.ScriptSnapshot.fromString('');
}
}
getScriptKind(_fileName) {
return ts.ScriptKind.TS;
return this._ts.ScriptKind.TS;
}
getCurrentDirectory() {
return '';

View file

@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as fs from 'fs';
import * as ts from 'typescript';
import type * as ts from 'typescript';
import * as path from 'path';
import * as fancyLog from 'fancy-log';
import * as ansiColors from 'ansi-colors';
@ -26,7 +26,7 @@ type SourceFileGetter = (moduleId: string) => ts.SourceFile | null;
type TSTopLevelDeclaration = ts.InterfaceDeclaration | ts.EnumDeclaration | ts.ClassDeclaration | ts.TypeAliasDeclaration | ts.FunctionDeclaration | ts.ModuleDeclaration;
type TSTopLevelDeclare = TSTopLevelDeclaration | ts.VariableStatement;
function isDeclaration(a: TSTopLevelDeclare): a is TSTopLevelDeclaration {
function isDeclaration(ts: typeof import('typescript'), a: TSTopLevelDeclare): a is TSTopLevelDeclaration {
return (
a.kind === ts.SyntaxKind.InterfaceDeclaration
|| a.kind === ts.SyntaxKind.EnumDeclaration
@ -37,7 +37,7 @@ function isDeclaration(a: TSTopLevelDeclare): a is TSTopLevelDeclaration {
);
}
function visitTopLevelDeclarations(sourceFile: ts.SourceFile, visitor: (node: TSTopLevelDeclare) => boolean): void {
function visitTopLevelDeclarations(ts: typeof import('typescript'), sourceFile: ts.SourceFile, visitor: (node: TSTopLevelDeclare) => boolean): void {
let stop = false;
let visit = (node: ts.Node): void => {
@ -66,9 +66,9 @@ function visitTopLevelDeclarations(sourceFile: ts.SourceFile, visitor: (node: TS
}
function getAllTopLevelDeclarations(sourceFile: ts.SourceFile): TSTopLevelDeclare[] {
function getAllTopLevelDeclarations(ts: typeof import('typescript'), sourceFile: ts.SourceFile): TSTopLevelDeclare[] {
let all: TSTopLevelDeclare[] = [];
visitTopLevelDeclarations(sourceFile, (node) => {
visitTopLevelDeclarations(ts, sourceFile, (node) => {
if (node.kind === ts.SyntaxKind.InterfaceDeclaration || node.kind === ts.SyntaxKind.ClassDeclaration || node.kind === ts.SyntaxKind.ModuleDeclaration) {
let interfaceDeclaration = <ts.InterfaceDeclaration>node;
let triviaStart = interfaceDeclaration.pos;
@ -90,10 +90,10 @@ function getAllTopLevelDeclarations(sourceFile: ts.SourceFile): TSTopLevelDeclar
}
function getTopLevelDeclaration(sourceFile: ts.SourceFile, typeName: string): TSTopLevelDeclare | null {
function getTopLevelDeclaration(ts: typeof import('typescript'), sourceFile: ts.SourceFile, typeName: string): TSTopLevelDeclare | null {
let result: TSTopLevelDeclare | null = null;
visitTopLevelDeclarations(sourceFile, (node) => {
if (isDeclaration(node) && node.name) {
visitTopLevelDeclarations(ts, sourceFile, (node) => {
if (isDeclaration(ts, node) && node.name) {
if (node.name.text === typeName) {
result = node;
return true /*stop*/;
@ -127,24 +127,24 @@ function hasModifier(modifiers: ts.NodeArray<ts.Modifier> | undefined, kind: ts.
return false;
}
function isStatic(member: ts.ClassElement | ts.TypeElement): boolean {
function isStatic(ts: typeof import('typescript'), member: ts.ClassElement | ts.TypeElement): boolean {
return hasModifier(member.modifiers, ts.SyntaxKind.StaticKeyword);
}
function isDefaultExport(declaration: ts.InterfaceDeclaration | ts.ClassDeclaration): boolean {
function isDefaultExport(ts: typeof import('typescript'), declaration: ts.InterfaceDeclaration | ts.ClassDeclaration): boolean {
return (
hasModifier(declaration.modifiers, ts.SyntaxKind.DefaultKeyword)
&& hasModifier(declaration.modifiers, ts.SyntaxKind.ExportKeyword)
);
}
function getMassagedTopLevelDeclarationText(sourceFile: ts.SourceFile, declaration: TSTopLevelDeclare, importName: string, usage: string[], enums: IEnumEntry[]): string {
function getMassagedTopLevelDeclarationText(ts: typeof import('typescript'), sourceFile: ts.SourceFile, declaration: TSTopLevelDeclare, importName: string, usage: string[], enums: IEnumEntry[]): string {
let result = getNodeText(sourceFile, declaration);
if (declaration.kind === ts.SyntaxKind.InterfaceDeclaration || declaration.kind === ts.SyntaxKind.ClassDeclaration) {
let interfaceDeclaration = <ts.InterfaceDeclaration | ts.ClassDeclaration>declaration;
const staticTypeName = (
isDefaultExport(interfaceDeclaration)
isDefaultExport(ts, interfaceDeclaration)
? `${importName}.default`
: `${importName}.${declaration.name!.text}`
);
@ -168,7 +168,7 @@ function getMassagedTopLevelDeclarationText(sourceFile: ts.SourceFile, declarati
} else {
const memberName = (<ts.Identifier | ts.StringLiteral>member.name).text;
const memberAccess = (memberName.indexOf('.') >= 0 ? `['${memberName}']` : `.${memberName}`);
if (isStatic(member)) {
if (isStatic(ts, member)) {
usage.push(`a = ${staticTypeName}${memberAccess};`);
} else {
usage.push(`a = (<${instanceTypeName}>b)${memberAccess};`);
@ -222,7 +222,7 @@ function getMassagedTopLevelDeclarationText(sourceFile: ts.SourceFile, declarati
return result;
}
function format(text: string, endl: string): string {
function format(ts: typeof import('typescript'), text: string, endl: string): string {
const REALLY_FORMAT = false;
text = preformat(text, endl);
@ -396,7 +396,7 @@ interface IEnumEntry {
text: string;
}
function generateDeclarationFile(recipe: string, sourceFileGetter: SourceFileGetter): ITempResult | null {
function generateDeclarationFile(ts: typeof import('typescript'), recipe: string, sourceFileGetter: SourceFileGetter): ITempResult | null {
const endl = /\r\n/.test(recipe) ? '\r\n' : '\n';
let lines = recipe.split(endl);
@ -452,14 +452,14 @@ function generateDeclarationFile(recipe: string, sourceFileGetter: SourceFileGet
if (typeName.length === 0) {
return;
}
let declaration = getTopLevelDeclaration(sourceFile, typeName);
let declaration = getTopLevelDeclaration(ts, sourceFile, typeName);
if (!declaration) {
logErr(`While handling ${line}`);
logErr(`Cannot find ${typeName}`);
failed = true;
return;
}
result.push(replacer(getMassagedTopLevelDeclarationText(sourceFile, declaration, importName, usage, enums)));
result.push(replacer(getMassagedTopLevelDeclarationText(ts, sourceFile, declaration, importName, usage, enums)));
});
return;
}
@ -491,8 +491,8 @@ function generateDeclarationFile(recipe: string, sourceFileGetter: SourceFileGet
typesToExcludeArr.push(typeName);
});
getAllTopLevelDeclarations(sourceFile).forEach((declaration) => {
if (isDeclaration(declaration) && declaration.name) {
getAllTopLevelDeclarations(ts, sourceFile).forEach((declaration) => {
if (isDeclaration(ts, declaration) && declaration.name) {
if (typesToExcludeMap[declaration.name.text]) {
return;
}
@ -505,7 +505,7 @@ function generateDeclarationFile(recipe: string, sourceFileGetter: SourceFileGet
}
}
}
result.push(replacer(getMassagedTopLevelDeclarationText(sourceFile, declaration, importName, usage, enums)));
result.push(replacer(getMassagedTopLevelDeclarationText(ts, sourceFile, declaration, importName, usage, enums)));
});
return;
}
@ -530,7 +530,7 @@ function generateDeclarationFile(recipe: string, sourceFileGetter: SourceFileGet
resultTxt = resultTxt.replace(/\bURI\b/g, 'Uri');
resultTxt = resultTxt.replace(/\bEvent</g, 'IEvent<');
resultTxt = resultTxt.split(/\r\n|\n|\r/).join(endl);
resultTxt = format(resultTxt, endl);
resultTxt = format(ts, resultTxt, endl);
resultTxt = resultTxt.split(/\r\n|\n|\r/).join(endl);
enums.sort((e1, e2) => {
@ -553,7 +553,7 @@ function generateDeclarationFile(recipe: string, sourceFileGetter: SourceFileGet
''
].concat(enums.map(e => e.text)).join(endl);
resultEnums = resultEnums.split(/\r\n|\n|\r/).join(endl);
resultEnums = format(resultEnums, endl);
resultEnums = format(ts, resultEnums, endl);
resultEnums = resultEnums.split(/\r\n|\n|\r/).join(endl);
return {
@ -571,9 +571,9 @@ export interface IMonacoDeclarationResult {
isTheSame: boolean;
}
function _run(sourceFileGetter: SourceFileGetter): IMonacoDeclarationResult | null {
function _run(ts: typeof import('typescript'), sourceFileGetter: SourceFileGetter): IMonacoDeclarationResult | null {
const recipe = fs.readFileSync(RECIPE_PATH).toString();
const t = generateDeclarationFile(recipe, sourceFileGetter);
const t = generateDeclarationFile(ts, recipe, sourceFileGetter);
if (!t) {
return null;
}
@ -617,9 +617,11 @@ class CacheEntry {
export class DeclarationResolver {
public readonly ts: typeof import('typescript');
private _sourceFileCache: { [moduleId: string]: CacheEntry | null; };
constructor(private readonly _fsProvider: FSProvider) {
this.ts = require('typescript') as typeof import('typescript');
this._sourceFileCache = Object.create(null);
}
@ -659,7 +661,7 @@ export class DeclarationResolver {
// const mtime = this._fsProvider.statFileSync()
const fileContents = this._fsProvider.readFileSync(moduleId, fileName).toString();
return new CacheEntry(
ts.createSourceFile(fileName, fileContents, ts.ScriptTarget.ES5),
this.ts.createSourceFile(fileName, fileContents, this.ts.ScriptTarget.ES5),
mtime
);
}
@ -667,10 +669,10 @@ export class DeclarationResolver {
const fileMap: IFileMap = {
'file.ts': fileContents
};
const service = ts.createLanguageService(new TypeScriptLanguageServiceHost({}, fileMap, {}));
const service = this.ts.createLanguageService(new TypeScriptLanguageServiceHost(this.ts, {}, fileMap, {}));
const text = service.getEmitOutput('file.ts', true, true).outputFiles[0].text;
return new CacheEntry(
ts.createSourceFile(fileName, text, ts.ScriptTarget.ES5),
this.ts.createSourceFile(fileName, text, this.ts.ScriptTarget.ES5),
mtime
);
}
@ -678,7 +680,7 @@ export class DeclarationResolver {
export function run3(resolver: DeclarationResolver): IMonacoDeclarationResult | null {
const sourceFileGetter = (moduleId: string) => resolver.getDeclarationSourceFile(moduleId);
return _run(sourceFileGetter);
return _run(resolver.ts, sourceFileGetter);
}
@ -689,11 +691,13 @@ interface IFileMap { [fileName: string]: string; }
class TypeScriptLanguageServiceHost implements ts.LanguageServiceHost {
private readonly _ts: typeof import('typescript');
private readonly _libs: ILibMap;
private readonly _files: IFileMap;
private readonly _compilerOptions: ts.CompilerOptions;
constructor(libs: ILibMap, files: IFileMap, compilerOptions: ts.CompilerOptions) {
constructor(ts: typeof import('typescript'), libs: ILibMap, files: IFileMap, compilerOptions: ts.CompilerOptions) {
this._ts = ts;
this._libs = libs;
this._files = files;
this._compilerOptions = compilerOptions;
@ -719,15 +723,15 @@ class TypeScriptLanguageServiceHost implements ts.LanguageServiceHost {
}
getScriptSnapshot(fileName: string): ts.IScriptSnapshot {
if (this._files.hasOwnProperty(fileName)) {
return ts.ScriptSnapshot.fromString(this._files[fileName]);
return this._ts.ScriptSnapshot.fromString(this._files[fileName]);
} else if (this._libs.hasOwnProperty(fileName)) {
return ts.ScriptSnapshot.fromString(this._libs[fileName]);
return this._ts.ScriptSnapshot.fromString(this._libs[fileName]);
} else {
return ts.ScriptSnapshot.fromString('');
return this._ts.ScriptSnapshot.fromString('');
}
}
getScriptKind(_fileName: string): ts.ScriptKind {
return ts.ScriptKind.TS;
return this._ts.ScriptKind.TS;
}
getCurrentDirectory(): string {
return '';

View file

@ -3,7 +3,8 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
const ts = require("typescript");
Object.defineProperty(exports, "__esModule", { value: true });
exports.nls = void 0;
const lazy = require("lazy.js");
const event_stream_1 = require("event-stream");
const File = require("vinyl");
@ -16,7 +17,7 @@ var CollectStepResult;
CollectStepResult[CollectStepResult["No"] = 2] = "No";
CollectStepResult[CollectStepResult["NoAndRecurse"] = 3] = "NoAndRecurse";
})(CollectStepResult || (CollectStepResult = {}));
function collect(node, fn) {
function collect(ts, node, fn) {
const result = [];
function loop(node) {
const stepResult = fn(node);
@ -69,14 +70,16 @@ function nls() {
if (!typescript) {
return this.emit('error', new Error(`File ${f.relative} does not have the original content in the source map.`));
}
nls.patchFiles(f, typescript).forEach(f => this.emit('data', f));
_nls.patchFiles(f, typescript).forEach(f => this.emit('data', f));
}));
return event_stream_1.duplex(input, output);
}
function isImportNode(node) {
exports.nls = nls;
function isImportNode(ts, node) {
return node.kind === ts.SyntaxKind.ImportDeclaration || node.kind === ts.SyntaxKind.ImportEqualsDeclaration;
}
(function (nls_1) {
var _nls;
(function (_nls) {
function fileFrom(file, contents, path = file.path) {
return new File({
contents: Buffer.from(contents),
@ -85,17 +88,14 @@ function isImportNode(node) {
path: path
});
}
nls_1.fileFrom = fileFrom;
function mappedPositionFrom(source, lc) {
return { source, line: lc.line + 1, column: lc.character };
}
nls_1.mappedPositionFrom = mappedPositionFrom;
function lcFrom(position) {
return { line: position.line - 1, character: position.column };
}
nls_1.lcFrom = lcFrom;
class SingleFileServiceHost {
constructor(options, filename, contents) {
constructor(ts, options, filename, contents) {
this.options = options;
this.filename = filename;
this.getCompilationSettings = () => this.options;
@ -108,20 +108,19 @@ function isImportNode(node) {
this.lib = ts.ScriptSnapshot.fromString('');
}
}
nls_1.SingleFileServiceHost = SingleFileServiceHost;
function isCallExpressionWithinTextSpanCollectStep(textSpan, node) {
function isCallExpressionWithinTextSpanCollectStep(ts, textSpan, node) {
if (!ts.textSpanContainsTextSpan({ start: node.pos, length: node.end - node.pos }, textSpan)) {
return CollectStepResult.No;
}
return node.kind === ts.SyntaxKind.CallExpression ? CollectStepResult.YesAndRecurse : CollectStepResult.NoAndRecurse;
}
function analyze(contents, options = {}) {
function analyze(ts, contents, options = {}) {
const filename = 'file.ts';
const serviceHost = new SingleFileServiceHost(Object.assign(clone(options), { noResolve: true }), filename, contents);
const serviceHost = new SingleFileServiceHost(ts, Object.assign(clone(options), { noResolve: true }), filename, contents);
const service = ts.createLanguageService(serviceHost);
const sourceFile = ts.createSourceFile(filename, contents, ts.ScriptTarget.ES5, true);
// all imports
const imports = lazy(collect(sourceFile, n => isImportNode(n) ? CollectStepResult.YesAndRecurse : CollectStepResult.NoAndRecurse));
const imports = lazy(collect(ts, sourceFile, n => isImportNode(ts, n) ? CollectStepResult.YesAndRecurse : CollectStepResult.NoAndRecurse));
// import nls = require('vs/nls');
const importEqualsDeclarations = imports
.filter(n => n.kind === ts.SyntaxKind.ImportEqualsDeclaration)
@ -152,7 +151,7 @@ function isImportNode(node) {
.flatten()
.filter(r => !r.isWriteAccess)
// find the deepest call expressions AST nodes that contain those references
.map(r => collect(sourceFile, n => isCallExpressionWithinTextSpanCollectStep(r.textSpan, n)))
.map(r => collect(ts, sourceFile, n => isCallExpressionWithinTextSpanCollectStep(ts, r.textSpan, n)))
.map(a => lazy(a).last())
.filter(n => !!n)
.map(n => n)
@ -178,7 +177,7 @@ function isImportNode(node) {
// find the deepest call expressions AST nodes that contain those references
const localizeCallExpressions = localizeReferences
.concat(namedLocalizeReferences)
.map(r => collect(sourceFile, n => isCallExpressionWithinTextSpanCollectStep(r.textSpan, n)))
.map(r => collect(ts, sourceFile, n => isCallExpressionWithinTextSpanCollectStep(ts, r.textSpan, n)))
.map(a => lazy(a).last())
.filter(n => !!n)
.map(n => n);
@ -199,7 +198,6 @@ function isImportNode(node) {
nlsExpressions: nlsExpressions.toArray()
};
}
nls_1.analyze = analyze;
class TextModel {
constructor(contents) {
const regex = /\r\n|\r|\n/g;
@ -250,9 +248,8 @@ function isImportNode(node) {
.flatten().toArray().join('');
}
}
nls_1.TextModel = TextModel;
function patchJavascript(patches, contents, moduleId) {
const model = new nls.TextModel(contents);
const model = new TextModel(contents);
// patch the localize calls
lazy(patches).reverse().each(p => model.apply(p));
// patch the 'vs/nls' imports
@ -261,7 +258,6 @@ function isImportNode(node) {
model.set(0, patchedFirstLine);
return model.toString();
}
nls_1.patchJavascript = patchJavascript;
function patchSourcemap(patches, rsm, smc) {
const smg = new sm.SourceMapGenerator({
file: rsm.file,
@ -297,9 +293,8 @@ function isImportNode(node) {
}
return JSON.parse(smg.toString());
}
nls_1.patchSourcemap = patchSourcemap;
function patch(moduleId, typescript, javascript, sourcemap) {
const { localizeCalls, nlsExpressions } = analyze(typescript);
function patch(ts, moduleId, typescript, javascript, sourcemap) {
const { localizeCalls, nlsExpressions } = analyze(ts, typescript);
if (localizeCalls.length === 0) {
return { javascript, sourcemap };
}
@ -332,13 +327,13 @@ function isImportNode(node) {
sourcemap = patchSourcemap(patches, sourcemap, smc);
return { javascript, sourcemap, nlsKeys, nls };
}
nls_1.patch = patch;
function patchFiles(javascriptFile, typescript) {
const ts = require('typescript');
// hack?
const moduleId = javascriptFile.relative
.replace(/\.js$/, '')
.replace(/\\/g, '/');
const { javascript, sourcemap, nlsKeys, nls } = patch(moduleId, typescript, javascriptFile.contents.toString(), javascriptFile.sourceMap);
const { javascript, sourcemap, nlsKeys, nls } = patch(ts, moduleId, typescript, javascriptFile.contents.toString(), javascriptFile.sourceMap);
const result = [fileFrom(javascriptFile, javascript)];
result[0].sourceMap = sourcemap;
if (nlsKeys) {
@ -349,6 +344,5 @@ function isImportNode(node) {
}
return result;
}
nls_1.patchFiles = patchFiles;
})(nls || (nls = {}));
module.exports = nls;
_nls.patchFiles = patchFiles;
})(_nls || (_nls = {}));

View file

@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as ts from 'typescript';
import type * as ts from 'typescript';
import * as lazy from 'lazy.js';
import { duplex, through } from 'event-stream';
import * as File from 'vinyl';
@ -21,7 +21,7 @@ enum CollectStepResult {
NoAndRecurse
}
function collect(node: ts.Node, fn: (node: ts.Node) => CollectStepResult): ts.Node[] {
function collect(ts: typeof import('typescript'), node: ts.Node, fn: (node: ts.Node) => CollectStepResult): ts.Node[] {
const result: ts.Node[] = [];
function loop(node: ts.Node) {
@ -65,7 +65,7 @@ define([], [${ wrap + lines.map(l => indent + l).join(',\n') + wrap}]);`;
/**
* Returns a stream containing the patched JavaScript and source maps.
*/
function nls(): NodeJS.ReadWriteStream {
export function nls(): NodeJS.ReadWriteStream {
const input = through();
const output = input.pipe(through(function (f: FileSourceMap) {
if (!f.sourceMap) {
@ -87,48 +87,48 @@ function nls(): NodeJS.ReadWriteStream {
return this.emit('error', new Error(`File ${f.relative} does not have the original content in the source map.`));
}
nls.patchFiles(f, typescript).forEach(f => this.emit('data', f));
_nls.patchFiles(f, typescript).forEach(f => this.emit('data', f));
}));
return duplex(input, output);
}
function isImportNode(node: ts.Node): boolean {
function isImportNode(ts: typeof import('typescript'), node: ts.Node): boolean {
return node.kind === ts.SyntaxKind.ImportDeclaration || node.kind === ts.SyntaxKind.ImportEqualsDeclaration;
}
module nls {
module _nls {
export interface INlsStringResult {
interface INlsStringResult {
javascript: string;
sourcemap: sm.RawSourceMap;
nls?: string;
nlsKeys?: string;
}
export interface ISpan {
interface ISpan {
start: ts.LineAndCharacter;
end: ts.LineAndCharacter;
}
export interface ILocalizeCall {
interface ILocalizeCall {
keySpan: ISpan;
key: string;
valueSpan: ISpan;
value: string;
}
export interface ILocalizeAnalysisResult {
interface ILocalizeAnalysisResult {
localizeCalls: ILocalizeCall[];
nlsExpressions: ISpan[];
}
export interface IPatch {
interface IPatch {
span: ISpan;
content: string;
}
export function fileFrom(file: File, contents: string, path: string = file.path) {
function fileFrom(file: File, contents: string, path: string = file.path) {
return new File({
contents: Buffer.from(contents),
base: file.base,
@ -137,20 +137,20 @@ module nls {
});
}
export function mappedPositionFrom(source: string, lc: ts.LineAndCharacter): sm.MappedPosition {
function mappedPositionFrom(source: string, lc: ts.LineAndCharacter): sm.MappedPosition {
return { source, line: lc.line + 1, column: lc.character };
}
export function lcFrom(position: sm.Position): ts.LineAndCharacter {
function lcFrom(position: sm.Position): ts.LineAndCharacter {
return { line: position.line - 1, character: position.column };
}
export class SingleFileServiceHost implements ts.LanguageServiceHost {
class SingleFileServiceHost implements ts.LanguageServiceHost {
private file: ts.IScriptSnapshot;
private lib: ts.IScriptSnapshot;
constructor(private options: ts.CompilerOptions, private filename: string, contents: string) {
constructor(ts: typeof import('typescript'), private options: ts.CompilerOptions, private filename: string, contents: string) {
this.file = ts.ScriptSnapshot.fromString(contents);
this.lib = ts.ScriptSnapshot.fromString('');
}
@ -163,7 +163,7 @@ module nls {
getDefaultLibFileName = () => 'lib.d.ts';
}
function isCallExpressionWithinTextSpanCollectStep(textSpan: ts.TextSpan, node: ts.Node): CollectStepResult {
function isCallExpressionWithinTextSpanCollectStep(ts: typeof import('typescript'), textSpan: ts.TextSpan, node: ts.Node): CollectStepResult {
if (!ts.textSpanContainsTextSpan({ start: node.pos, length: node.end - node.pos }, textSpan)) {
return CollectStepResult.No;
}
@ -171,14 +171,14 @@ module nls {
return node.kind === ts.SyntaxKind.CallExpression ? CollectStepResult.YesAndRecurse : CollectStepResult.NoAndRecurse;
}
export function analyze(contents: string, options: ts.CompilerOptions = {}): ILocalizeAnalysisResult {
function analyze(ts: typeof import('typescript'), contents: string, options: ts.CompilerOptions = {}): ILocalizeAnalysisResult {
const filename = 'file.ts';
const serviceHost = new SingleFileServiceHost(Object.assign(clone(options), { noResolve: true }), filename, contents);
const serviceHost = new SingleFileServiceHost(ts, Object.assign(clone(options), { noResolve: true }), filename, contents);
const service = ts.createLanguageService(serviceHost);
const sourceFile = ts.createSourceFile(filename, contents, ts.ScriptTarget.ES5, true);
// all imports
const imports = lazy(collect(sourceFile, n => isImportNode(n) ? CollectStepResult.YesAndRecurse : CollectStepResult.NoAndRecurse));
const imports = lazy(collect(ts, sourceFile, n => isImportNode(ts, n) ? CollectStepResult.YesAndRecurse : CollectStepResult.NoAndRecurse));
// import nls = require('vs/nls');
const importEqualsDeclarations = imports
@ -215,7 +215,7 @@ module nls {
.filter(r => !r.isWriteAccess)
// find the deepest call expressions AST nodes that contain those references
.map(r => collect(sourceFile, n => isCallExpressionWithinTextSpanCollectStep(r.textSpan, n)))
.map(r => collect(ts, sourceFile, n => isCallExpressionWithinTextSpanCollectStep(ts, r.textSpan, n)))
.map(a => lazy(a).last())
.filter(n => !!n)
.map(n => <ts.CallExpression>n)
@ -246,7 +246,7 @@ module nls {
// find the deepest call expressions AST nodes that contain those references
const localizeCallExpressions = localizeReferences
.concat(namedLocalizeReferences)
.map(r => collect(sourceFile, n => isCallExpressionWithinTextSpanCollectStep(r.textSpan, n)))
.map(r => collect(ts, sourceFile, n => isCallExpressionWithinTextSpanCollectStep(ts, r.textSpan, n)))
.map(a => lazy(a).last())
.filter(n => !!n)
.map(n => <ts.CallExpression>n);
@ -270,7 +270,7 @@ module nls {
};
}
export class TextModel {
class TextModel {
private lines: string[];
private lineEndings: string[];
@ -336,8 +336,8 @@ module nls {
}
}
export function patchJavascript(patches: IPatch[], contents: string, moduleId: string): string {
const model = new nls.TextModel(contents);
function patchJavascript(patches: IPatch[], contents: string, moduleId: string): string {
const model = new TextModel(contents);
// patch the localize calls
lazy(patches).reverse().each(p => model.apply(p));
@ -350,7 +350,7 @@ module nls {
return model.toString();
}
export function patchSourcemap(patches: IPatch[], rsm: sm.RawSourceMap, smc: sm.SourceMapConsumer): sm.RawSourceMap {
function patchSourcemap(patches: IPatch[], rsm: sm.RawSourceMap, smc: sm.SourceMapConsumer): sm.RawSourceMap {
const smg = new sm.SourceMapGenerator({
file: rsm.file,
sourceRoot: rsm.sourceRoot
@ -395,8 +395,8 @@ module nls {
return JSON.parse(smg.toString());
}
export function patch(moduleId: string, typescript: string, javascript: string, sourcemap: sm.RawSourceMap): INlsStringResult {
const { localizeCalls, nlsExpressions } = analyze(typescript);
function patch(ts: typeof import('typescript'), moduleId: string, typescript: string, javascript: string, sourcemap: sm.RawSourceMap): INlsStringResult {
const { localizeCalls, nlsExpressions } = analyze(ts, typescript);
if (localizeCalls.length === 0) {
return { javascript, sourcemap };
@ -438,12 +438,14 @@ module nls {
}
export function patchFiles(javascriptFile: File, typescript: string): File[] {
const ts = require('typescript') as typeof import('typescript');
// hack?
const moduleId = javascriptFile.relative
.replace(/\.js$/, '')
.replace(/\\/g, '/');
const { javascript, sourcemap, nlsKeys, nls } = patch(
ts,
moduleId,
typescript,
javascriptFile.contents.toString(),
@ -464,5 +466,3 @@ module nls {
return result;
}
}
export = nls;

View file

@ -8,17 +8,11 @@ exports.minifyTask = exports.optimizeTask = exports.loaderConfig = void 0;
const es = require("event-stream");
const gulp = require("gulp");
const concat = require("gulp-concat");
const minifyCSS = require("gulp-cssnano");
const filter = require("gulp-filter");
const flatmap = require("gulp-flatmap");
const sourcemaps = require("gulp-sourcemaps");
const uglify = require("gulp-uglify");
const composer = require("gulp-uglify/composer");
const fancyLog = require("fancy-log");
const ansiColors = require("ansi-colors");
const path = require("path");
const pump = require("pump");
const terser = require("terser");
const VinylFile = require("vinyl");
const bundle = require("./bundle");
const i18n_1 = require("./i18n");
@ -124,6 +118,7 @@ function optimizeTask(opts) {
const out = opts.out;
const fileContentMapper = opts.fileContentMapper || ((contents, _path) => contents);
return function () {
const sourcemaps = require('gulp-sourcemaps');
const bundlesStream = es.through(); // this stream will contain the bundled files
const resourcesStream = es.through(); // this stream will contain the resources
const bundleInfoStream = es.through(); // this stream will contain bundleInfo.json
@ -166,55 +161,31 @@ function optimizeTask(opts) {
};
}
exports.optimizeTask = optimizeTask;
/**
* Wrap around uglify and allow the preserveComments function
* to have a file "context" to include our copyright only once per file.
*/
function uglifyWithCopyrights() {
const preserveComments = (f) => {
return (_node, comment) => {
const text = comment.value;
const type = comment.type;
if (/@minifier_do_not_preserve/.test(text)) {
return false;
}
const isOurCopyright = IS_OUR_COPYRIGHT_REGEXP.test(text);
if (isOurCopyright) {
if (f.__hasOurCopyright) {
return false;
}
f.__hasOurCopyright = true;
return true;
}
if ('comment2' === type) {
// check for /*!. Note that text doesn't contain leading /*
return (text.length > 0 && text[0] === '!') || /@preserve|license|@cc_on|copyright/i.test(text);
}
else if ('comment1' === type) {
return /license|copyright/i.test(text);
}
return false;
};
};
const minify = composer(terser);
const input = es.through();
const output = input
.pipe(flatmap((stream, f) => {
return stream.pipe(minify({
output: {
comments: preserveComments(f),
max_line_len: 1024
}
}));
}));
return es.duplex(input, output);
}
function minifyTask(src, sourceMapBaseUrl) {
const esbuild = require('esbuild');
const sourceMappingURL = sourceMapBaseUrl ? ((f) => `${sourceMapBaseUrl}/${f.relative}.map`) : undefined;
return cb => {
const minifyCSS = require('gulp-cssnano');
const sourcemaps = require('gulp-sourcemaps');
const jsFilter = filter('**/*.js', { restore: true });
const cssFilter = filter('**/*.css', { restore: true });
pump(gulp.src([src + '/**', '!' + src + '/**/*.map']), jsFilter, sourcemaps.init({ loadMaps: true }), uglifyWithCopyrights(), jsFilter.restore, cssFilter, minifyCSS({ reduceIdents: false }), cssFilter.restore, sourcemaps.mapSources((sourcePath) => {
pump(gulp.src([src + '/**', '!' + src + '/**/*.map']), jsFilter, sourcemaps.init({ loadMaps: true }), es.map((f, cb) => {
esbuild.build({
entryPoints: [f.path],
minify: true,
sourcemap: 'external',
outdir: '.',
platform: 'node',
target: ['node12.18'],
write: false
}).then(res => {
const jsFile = res.outputFiles.find(f => /\.js$/.test(f.path));
const sourceMapFile = res.outputFiles.find(f => /\.js\.map$/.test(f.path));
f.contents = Buffer.from(jsFile.contents);
f.sourceMap = JSON.parse(sourceMapFile.text);
cb(undefined, f);
}, cb);
}), jsFilter.restore, cssFilter, minifyCSS({ reduceIdents: false }), cssFilter.restore, sourcemaps.mapSources((sourcePath) => {
if (sourcePath === 'bootstrap-fork.js') {
return 'bootstrap-fork.orig.js';
}
@ -224,12 +195,7 @@ function minifyTask(src, sourceMapBaseUrl) {
sourceRoot: undefined,
includeContent: true,
addComment: true
}), gulp.dest(src + '-min'), (err) => {
if (err instanceof uglify.GulpUglifyError) {
console.error(`Uglify error in '${err.cause && err.cause.filename}'`);
}
cb(err);
});
}), gulp.dest(src + '-min'), (err) => cb(err));
};
}
exports.minifyTask = minifyTask;

View file

@ -8,17 +8,11 @@
import * as es from 'event-stream';
import * as gulp from 'gulp';
import * as concat from 'gulp-concat';
import * as minifyCSS from 'gulp-cssnano';
import * as filter from 'gulp-filter';
import * as flatmap from 'gulp-flatmap';
import * as sourcemaps from 'gulp-sourcemaps';
import * as uglify from 'gulp-uglify';
import * as composer from 'gulp-uglify/composer';
import * as fancyLog from 'fancy-log';
import * as ansiColors from 'ansi-colors';
import * as path from 'path';
import * as pump from 'pump';
import * as terser from 'terser';
import * as VinylFile from 'vinyl';
import * as bundle from './bundle';
import { Language, processNlsFiles } from './i18n';
@ -184,6 +178,8 @@ export function optimizeTask(opts: IOptimizeTaskOpts): () => NodeJS.ReadWriteStr
const fileContentMapper = opts.fileContentMapper || ((contents: string, _path: string) => contents);
return function () {
const sourcemaps = require('gulp-sourcemaps') as typeof import('gulp-sourcemaps');
const bundlesStream = es.through(); // this stream will contain the bundled files
const resourcesStream = es.through(); // this stream will contain the resources
const bundleInfoStream = es.through(); // this stream will contain bundleInfo.json
@ -235,62 +231,14 @@ export function optimizeTask(opts: IOptimizeTaskOpts): () => NodeJS.ReadWriteStr
};
}
declare class FileWithCopyright extends VinylFile {
public __hasOurCopyright: boolean;
}
/**
* Wrap around uglify and allow the preserveComments function
* to have a file "context" to include our copyright only once per file.
*/
function uglifyWithCopyrights(): NodeJS.ReadWriteStream {
const preserveComments = (f: FileWithCopyright) => {
return (_node: any, comment: { value: string; type: string; }) => {
const text = comment.value;
const type = comment.type;
if (/@minifier_do_not_preserve/.test(text)) {
return false;
}
const isOurCopyright = IS_OUR_COPYRIGHT_REGEXP.test(text);
if (isOurCopyright) {
if (f.__hasOurCopyright) {
return false;
}
f.__hasOurCopyright = true;
return true;
}
if ('comment2' === type) {
// check for /*!. Note that text doesn't contain leading /*
return (text.length > 0 && text[0] === '!') || /@preserve|license|@cc_on|copyright/i.test(text);
} else if ('comment1' === type) {
return /license|copyright/i.test(text);
}
return false;
};
};
const minify = (composer as any)(terser);
const input = es.through();
const output = input
.pipe(flatmap((stream, f) => {
return stream.pipe(minify({
output: {
comments: preserveComments(<FileWithCopyright>f),
max_line_len: 1024
}
}));
}));
return es.duplex(input, output);
}
export function minifyTask(src: string, sourceMapBaseUrl?: string): (cb: any) => void {
const esbuild = require('esbuild') as typeof import('esbuild');
const sourceMappingURL = sourceMapBaseUrl ? ((f: any) => `${sourceMapBaseUrl}/${f.relative}.map`) : undefined;
return cb => {
const minifyCSS = require('gulp-cssnano') as typeof import('gulp-cssnano');
const sourcemaps = require('gulp-sourcemaps') as typeof import('gulp-sourcemaps');
const jsFilter = filter('**/*.js', { restore: true });
const cssFilter = filter('**/*.css', { restore: true });
@ -298,7 +246,25 @@ export function minifyTask(src: string, sourceMapBaseUrl?: string): (cb: any) =>
gulp.src([src + '/**', '!' + src + '/**/*.map']),
jsFilter,
sourcemaps.init({ loadMaps: true }),
uglifyWithCopyrights(),
es.map((f: any, cb) => {
esbuild.build({
entryPoints: [f.path],
minify: true,
sourcemap: 'external',
outdir: '.',
platform: 'node',
target: ['node12.18'],
write: false
}).then(res => {
const jsFile = res.outputFiles.find(f => /\.js$/.test(f.path))!;
const sourceMapFile = res.outputFiles.find(f => /\.js\.map$/.test(f.path))!;
f.contents = Buffer.from(jsFile.contents);
f.sourceMap = JSON.parse(sourceMapFile.text);
cb(undefined, f);
}, cb);
}),
jsFilter.restore,
cssFilter,
minifyCSS({ reduceIdents: false }),
@ -316,13 +282,7 @@ export function minifyTask(src: string, sourceMapBaseUrl?: string): (cb: any) =>
includeContent: true,
addComment: true
} as any),
gulp.dest(src + '-min')
, (err: any) => {
if (err instanceof (uglify as any).GulpUglifyError) {
console.error(`Uglify error in '${err.cause && err.cause.filename}'`);
}
cb(err);
});
gulp.dest(src + '-min'),
(err: any) => cb(err));
};
}

View file

@ -5,7 +5,6 @@
*--------------------------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.createESMSourcesAndResources2 = exports.extractEditor = void 0;
const ts = require("typescript");
const fs = require("fs");
const path = require("path");
const tss = require("./treeshaking");
@ -29,6 +28,7 @@ function writeFile(filePath, contents) {
}
function extractEditor(options) {
var _a;
const ts = require('typescript');
const tsConfig = JSON.parse(fs.readFileSync(path.join(options.sourcesRoot, 'tsconfig.monaco.json')).toString());
let compilerOptions;
if (tsConfig.extends) {
@ -115,6 +115,7 @@ function extractEditor(options) {
}
exports.extractEditor = extractEditor;
function createESMSourcesAndResources2(options) {
const ts = require('typescript');
const SRC_FOLDER = path.join(REPO_ROOT, options.srcFolder);
const OUT_FOLDER = path.join(REPO_ROOT, options.outFolder);
const OUT_RESOURCES_FOLDER = path.join(REPO_ROOT, options.outResourcesFolder);

View file

@ -3,7 +3,6 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as ts from 'typescript';
import * as fs from 'fs';
import * as path from 'path';
import * as tss from './treeshaking';
@ -31,6 +30,8 @@ function writeFile(filePath: string, contents: Buffer | string): void {
}
export function extractEditor(options: tss.ITreeShakingOptions & { destRoot: string }): void {
const ts = require('typescript') as typeof import('typescript');
const tsConfig = JSON.parse(fs.readFileSync(path.join(options.sourcesRoot, 'tsconfig.monaco.json')).toString());
let compilerOptions: { [key: string]: any };
if (tsConfig.extends) {
@ -134,6 +135,8 @@ export interface IOptions2 {
}
export function createESMSourcesAndResources2(options: IOptions2): void {
const ts = require('typescript') as typeof import('typescript');
const SRC_FOLDER = path.join(REPO_ROOT, options.srcFolder);
const OUT_FOLDER = path.join(REPO_ROOT, options.outFolder);
const OUT_RESOURCES_FOLDER = path.join(REPO_ROOT, options.outResourcesFolder);

View file

@ -8,7 +8,6 @@ exports.submitAllStats = exports.createStatsStream = void 0;
const es = require("event-stream");
const fancyLog = require("fancy-log");
const ansiColors = require("ansi-colors");
const appInsights = require("applicationinsights");
class Entry {
constructor(name, totalCount, totalSize) {
this.name = name;
@ -73,6 +72,7 @@ function createStatsStream(group, log) {
}
exports.createStatsStream = createStatsStream;
function submitAllStats(productJson, commit) {
const appInsights = require('applicationinsights');
const sorted = [];
// move entries for single files to the front
_entries.forEach(value => {

View file

@ -9,7 +9,6 @@ import * as es from 'event-stream';
import * as fancyLog from 'fancy-log';
import * as ansiColors from 'ansi-colors';
import * as File from 'vinyl';
import * as appInsights from 'applicationinsights';
class Entry {
constructor(readonly name: string, public totalCount: number, public totalSize: number) { }
@ -75,6 +74,7 @@ export function createStatsStream(group: string, log?: boolean): es.ThroughStrea
}
export function submitAllStats(productJson: any, commit: string): Promise<boolean> {
const appInsights = require('applicationinsights') as typeof import('applicationinsights');
const sorted: Entry[] = [];
// move entries for single files to the front

View file

@ -7,7 +7,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
exports.shake = exports.toStringShakeLevel = exports.ShakeLevel = void 0;
const fs = require("fs");
const path = require("path");
const ts = require("typescript");
const TYPESCRIPT_LIB_FOLDER = path.dirname(require.resolve('typescript/lib/lib.d.ts'));
var ShakeLevel;
(function (ShakeLevel) {
@ -41,7 +40,8 @@ function printDiagnostics(options, diagnostics) {
}
}
function shake(options) {
const languageService = createTypeScriptLanguageService(options);
const ts = require('typescript');
const languageService = createTypeScriptLanguageService(ts, options);
const program = languageService.getProgram();
const globalDiagnostics = program.getGlobalDiagnostics();
if (globalDiagnostics.length > 0) {
@ -58,14 +58,14 @@ function shake(options) {
printDiagnostics(options, semanticDiagnostics);
throw new Error(`Compilation Errors encountered.`);
}
markNodes(languageService, options);
return generateResult(languageService, options.shakeLevel);
markNodes(ts, languageService, options);
return generateResult(ts, languageService, options.shakeLevel);
}
exports.shake = shake;
//#region Discovery, LanguageService & Setup
function createTypeScriptLanguageService(options) {
function createTypeScriptLanguageService(ts, options) {
// Discover referenced files
const FILES = discoverAndReadFiles(options);
const FILES = discoverAndReadFiles(ts, options);
// Add fake usage files
options.inlineEntryPoints.forEach((inlineEntryPoint, index) => {
FILES[`inlineEntryPoint.${index}.ts`] = inlineEntryPoint;
@ -76,15 +76,15 @@ function createTypeScriptLanguageService(options) {
FILES[typing] = fs.readFileSync(filePath).toString();
});
// Resolve libs
const RESOLVED_LIBS = processLibFiles(options);
const RESOLVED_LIBS = processLibFiles(ts, options);
const compilerOptions = ts.convertCompilerOptionsFromJson(options.compilerOptions, options.sourcesRoot).options;
const host = new TypeScriptLanguageServiceHost(RESOLVED_LIBS, FILES, compilerOptions);
const host = new TypeScriptLanguageServiceHost(ts, RESOLVED_LIBS, FILES, compilerOptions);
return ts.createLanguageService(host);
}
/**
* Read imports and follow them until all files have been handled
*/
function discoverAndReadFiles(options) {
function discoverAndReadFiles(ts, options) {
const FILES = {};
const in_queue = Object.create(null);
const queue = [];
@ -137,7 +137,7 @@ function discoverAndReadFiles(options) {
/**
* Read lib files and follow lib references
*/
function processLibFiles(options) {
function processLibFiles(ts, options) {
const stack = [...options.compilerOptions.lib];
const result = {};
while (stack.length > 0) {
@ -161,7 +161,8 @@ function processLibFiles(options) {
* A TypeScript language service host
*/
class TypeScriptLanguageServiceHost {
constructor(libs, files, compilerOptions) {
constructor(ts, libs, files, compilerOptions) {
this._ts = ts;
this._libs = libs;
this._files = files;
this._compilerOptions = compilerOptions;
@ -183,17 +184,17 @@ class TypeScriptLanguageServiceHost {
}
getScriptSnapshot(fileName) {
if (this._files.hasOwnProperty(fileName)) {
return ts.ScriptSnapshot.fromString(this._files[fileName]);
return this._ts.ScriptSnapshot.fromString(this._files[fileName]);
}
else if (this._libs.hasOwnProperty(fileName)) {
return ts.ScriptSnapshot.fromString(this._libs[fileName]);
return this._ts.ScriptSnapshot.fromString(this._libs[fileName]);
}
else {
return ts.ScriptSnapshot.fromString('');
return this._ts.ScriptSnapshot.fromString('');
}
}
getScriptKind(_fileName) {
return ts.ScriptKind.TS;
return this._ts.ScriptKind.TS;
}
getCurrentDirectory() {
return '';
@ -240,7 +241,7 @@ function nodeOrChildIsBlack(node) {
}
return false;
}
function markNodes(languageService, options) {
function markNodes(ts, languageService, options) {
const program = languageService.getProgram();
if (!program) {
throw new Error('Could not get program from language service');
@ -342,7 +343,7 @@ function markNodes(languageService, options) {
if (!referenceSourceFile) {
continue;
}
const referenceNode = getTokenAtPosition(referenceSourceFile, reference.textSpan.start, false, false);
const referenceNode = getTokenAtPosition(ts, referenceSourceFile, reference.textSpan.start, false, false);
if (ts.isMethodDeclaration(referenceNode.parent)
|| ts.isPropertyDeclaration(referenceNode.parent)
|| ts.isGetAccessor(referenceNode.parent)
@ -408,7 +409,7 @@ function markNodes(languageService, options) {
}
const nodeSourceFile = node.getSourceFile();
const loop = (node) => {
const [symbol, symbolImportNode] = getRealNodeSymbol(checker, node);
const [symbol, symbolImportNode] = getRealNodeSymbol(ts, checker, node);
if (symbolImportNode) {
setColor(symbolImportNode, 2 /* Black */);
}
@ -420,7 +421,7 @@ function markNodes(languageService, options) {
// (they can be the declaration of a module import)
continue;
}
if (options.shakeLevel === 2 /* ClassMembers */ && (ts.isClassDeclaration(declaration) || ts.isInterfaceDeclaration(declaration)) && !isLocalCodeExtendingOrInheritingFromDefaultLibSymbol(program, checker, declaration)) {
if (options.shakeLevel === 2 /* ClassMembers */ && (ts.isClassDeclaration(declaration) || ts.isInterfaceDeclaration(declaration)) && !isLocalCodeExtendingOrInheritingFromDefaultLibSymbol(ts, program, checker, declaration)) {
enqueue_black(declaration.name);
for (let j = 0; j < declaration.members.length; j++) {
const member = declaration.members[j];
@ -484,7 +485,7 @@ function nodeIsInItsOwnDeclaration(nodeSourceFile, node, symbol) {
}
return false;
}
function generateResult(languageService, shakeLevel) {
function generateResult(ts, languageService, shakeLevel) {
const program = languageService.getProgram();
if (!program) {
throw new Error('Could not get program from language service');
@ -614,11 +615,11 @@ function generateResult(languageService, shakeLevel) {
}
//#endregion
//#region Utils
function isLocalCodeExtendingOrInheritingFromDefaultLibSymbol(program, checker, declaration) {
function isLocalCodeExtendingOrInheritingFromDefaultLibSymbol(ts, program, checker, declaration) {
if (!program.isSourceFileDefaultLibrary(declaration.getSourceFile()) && declaration.heritageClauses) {
for (const heritageClause of declaration.heritageClauses) {
for (const type of heritageClause.types) {
const symbol = findSymbolFromHeritageType(checker, type);
const symbol = findSymbolFromHeritageType(ts, checker, type);
if (symbol) {
const decl = symbol.valueDeclaration || (symbol.declarations && symbol.declarations[0]);
if (decl && program.isSourceFileDefaultLibrary(decl.getSourceFile())) {
@ -630,22 +631,22 @@ function isLocalCodeExtendingOrInheritingFromDefaultLibSymbol(program, checker,
}
return false;
}
function findSymbolFromHeritageType(checker, type) {
function findSymbolFromHeritageType(ts, checker, type) {
if (ts.isExpressionWithTypeArguments(type)) {
return findSymbolFromHeritageType(checker, type.expression);
return findSymbolFromHeritageType(ts, checker, type.expression);
}
if (ts.isIdentifier(type)) {
return getRealNodeSymbol(checker, type)[0];
return getRealNodeSymbol(ts, checker, type)[0];
}
if (ts.isPropertyAccessExpression(type)) {
return findSymbolFromHeritageType(checker, type.name);
return findSymbolFromHeritageType(ts, checker, type.name);
}
return null;
}
/**
* Returns the node's symbol and the `import` node (if the symbol resolved from a different module)
*/
function getRealNodeSymbol(checker, node) {
function getRealNodeSymbol(ts, checker, node) {
const getPropertySymbolsFromContextualType = ts.getPropertySymbolsFromContextualType;
const getContainingObjectLiteralElement = ts.getContainingObjectLiteralElement;
const getNameFromPropertyName = ts.getNameFromPropertyName;
@ -758,7 +759,7 @@ function getRealNodeSymbol(checker, node) {
return [null, null];
}
/** Get the token whose text contains the position */
function getTokenAtPosition(sourceFile, position, allowPositionInLeadingTrivia, includeEndPosition) {
function getTokenAtPosition(ts, sourceFile, position, allowPositionInLeadingTrivia, includeEndPosition) {
let current = sourceFile;
outer: while (true) {
// find the child that contains 'position'

View file

@ -7,7 +7,7 @@
import * as fs from 'fs';
import * as path from 'path';
import * as ts from 'typescript';
import type * as ts from 'typescript';
const TYPESCRIPT_LIB_FOLDER = path.dirname(require.resolve('typescript/lib/lib.d.ts'));
@ -82,7 +82,8 @@ function printDiagnostics(options: ITreeShakingOptions, diagnostics: ReadonlyArr
}
export function shake(options: ITreeShakingOptions): ITreeShakingResult {
const languageService = createTypeScriptLanguageService(options);
const ts = require('typescript') as typeof import('typescript');
const languageService = createTypeScriptLanguageService(ts, options);
const program = languageService.getProgram()!;
const globalDiagnostics = program.getGlobalDiagnostics();
@ -103,15 +104,15 @@ export function shake(options: ITreeShakingOptions): ITreeShakingResult {
throw new Error(`Compilation Errors encountered.`);
}
markNodes(languageService, options);
markNodes(ts, languageService, options);
return generateResult(languageService, options.shakeLevel);
return generateResult(ts, languageService, options.shakeLevel);
}
//#region Discovery, LanguageService & Setup
function createTypeScriptLanguageService(options: ITreeShakingOptions): ts.LanguageService {
function createTypeScriptLanguageService(ts: typeof import('typescript'), options: ITreeShakingOptions): ts.LanguageService {
// Discover referenced files
const FILES = discoverAndReadFiles(options);
const FILES = discoverAndReadFiles(ts, options);
// Add fake usage files
options.inlineEntryPoints.forEach((inlineEntryPoint, index) => {
@ -125,18 +126,18 @@ function createTypeScriptLanguageService(options: ITreeShakingOptions): ts.Langu
});
// Resolve libs
const RESOLVED_LIBS = processLibFiles(options);
const RESOLVED_LIBS = processLibFiles(ts, options);
const compilerOptions = ts.convertCompilerOptionsFromJson(options.compilerOptions, options.sourcesRoot).options;
const host = new TypeScriptLanguageServiceHost(RESOLVED_LIBS, FILES, compilerOptions);
const host = new TypeScriptLanguageServiceHost(ts, RESOLVED_LIBS, FILES, compilerOptions);
return ts.createLanguageService(host);
}
/**
* Read imports and follow them until all files have been handled
*/
function discoverAndReadFiles(options: ITreeShakingOptions): IFileMap {
function discoverAndReadFiles(ts: typeof import('typescript'), options: ITreeShakingOptions): IFileMap {
const FILES: IFileMap = {};
const in_queue: { [module: string]: boolean; } = Object.create(null);
@ -199,7 +200,7 @@ function discoverAndReadFiles(options: ITreeShakingOptions): IFileMap {
/**
* Read lib files and follow lib references
*/
function processLibFiles(options: ITreeShakingOptions): ILibMap {
function processLibFiles(ts: typeof import('typescript'), options: ITreeShakingOptions): ILibMap {
const stack: string[] = [...options.compilerOptions.lib];
const result: ILibMap = {};
@ -232,11 +233,13 @@ interface IFileMap { [fileName: string]: string; }
*/
class TypeScriptLanguageServiceHost implements ts.LanguageServiceHost {
private readonly _ts: typeof import('typescript');
private readonly _libs: ILibMap;
private readonly _files: IFileMap;
private readonly _compilerOptions: ts.CompilerOptions;
constructor(libs: ILibMap, files: IFileMap, compilerOptions: ts.CompilerOptions) {
constructor(ts: typeof import('typescript'), libs: ILibMap, files: IFileMap, compilerOptions: ts.CompilerOptions) {
this._ts = ts;
this._libs = libs;
this._files = files;
this._compilerOptions = compilerOptions;
@ -262,15 +265,15 @@ class TypeScriptLanguageServiceHost implements ts.LanguageServiceHost {
}
getScriptSnapshot(fileName: string): ts.IScriptSnapshot {
if (this._files.hasOwnProperty(fileName)) {
return ts.ScriptSnapshot.fromString(this._files[fileName]);
return this._ts.ScriptSnapshot.fromString(this._files[fileName]);
} else if (this._libs.hasOwnProperty(fileName)) {
return ts.ScriptSnapshot.fromString(this._libs[fileName]);
return this._ts.ScriptSnapshot.fromString(this._libs[fileName]);
} else {
return ts.ScriptSnapshot.fromString('');
return this._ts.ScriptSnapshot.fromString('');
}
}
getScriptKind(_fileName: string): ts.ScriptKind {
return ts.ScriptKind.TS;
return this._ts.ScriptKind.TS;
}
getCurrentDirectory(): string {
return '';
@ -320,7 +323,7 @@ function nodeOrChildIsBlack(node: ts.Node): boolean {
return false;
}
function markNodes(languageService: ts.LanguageService, options: ITreeShakingOptions) {
function markNodes(ts: typeof import('typescript'), languageService: ts.LanguageService, options: ITreeShakingOptions) {
const program = languageService.getProgram();
if (!program) {
throw new Error('Could not get program from language service');
@ -446,7 +449,7 @@ function markNodes(languageService: ts.LanguageService, options: ITreeShakingOpt
continue;
}
const referenceNode = getTokenAtPosition(referenceSourceFile, reference.textSpan.start, false, false);
const referenceNode = getTokenAtPosition(ts, referenceSourceFile, reference.textSpan.start, false, false);
if (
ts.isMethodDeclaration(referenceNode.parent)
|| ts.isPropertyDeclaration(referenceNode.parent)
@ -522,7 +525,7 @@ function markNodes(languageService: ts.LanguageService, options: ITreeShakingOpt
const nodeSourceFile = node.getSourceFile();
const loop = (node: ts.Node) => {
const [symbol, symbolImportNode] = getRealNodeSymbol(checker, node);
const [symbol, symbolImportNode] = getRealNodeSymbol(ts, checker, node);
if (symbolImportNode) {
setColor(symbolImportNode, NodeColor.Black);
}
@ -536,7 +539,7 @@ function markNodes(languageService: ts.LanguageService, options: ITreeShakingOpt
continue;
}
if (options.shakeLevel === ShakeLevel.ClassMembers && (ts.isClassDeclaration(declaration) || ts.isInterfaceDeclaration(declaration)) && !isLocalCodeExtendingOrInheritingFromDefaultLibSymbol(program, checker, declaration)) {
if (options.shakeLevel === ShakeLevel.ClassMembers && (ts.isClassDeclaration(declaration) || ts.isInterfaceDeclaration(declaration)) && !isLocalCodeExtendingOrInheritingFromDefaultLibSymbol(ts, program, checker, declaration)) {
enqueue_black(declaration.name!);
for (let j = 0; j < declaration.members.length; j++) {
@ -607,7 +610,7 @@ function nodeIsInItsOwnDeclaration(nodeSourceFile: ts.SourceFile, node: ts.Node,
return false;
}
function generateResult(languageService: ts.LanguageService, shakeLevel: ShakeLevel): ITreeShakingResult {
function generateResult(ts: typeof import('typescript'), languageService: ts.LanguageService, shakeLevel: ShakeLevel): ITreeShakingResult {
const program = languageService.getProgram();
if (!program) {
throw new Error('Could not get program from language service');
@ -752,11 +755,11 @@ function generateResult(languageService: ts.LanguageService, shakeLevel: ShakeLe
//#region Utils
function isLocalCodeExtendingOrInheritingFromDefaultLibSymbol(program: ts.Program, checker: ts.TypeChecker, declaration: ts.ClassDeclaration | ts.InterfaceDeclaration): boolean {
function isLocalCodeExtendingOrInheritingFromDefaultLibSymbol(ts: typeof import('typescript'), program: ts.Program, checker: ts.TypeChecker, declaration: ts.ClassDeclaration | ts.InterfaceDeclaration): boolean {
if (!program.isSourceFileDefaultLibrary(declaration.getSourceFile()) && declaration.heritageClauses) {
for (const heritageClause of declaration.heritageClauses) {
for (const type of heritageClause.types) {
const symbol = findSymbolFromHeritageType(checker, type);
const symbol = findSymbolFromHeritageType(ts, checker, type);
if (symbol) {
const decl = symbol.valueDeclaration || (symbol.declarations && symbol.declarations[0]);
if (decl && program.isSourceFileDefaultLibrary(decl.getSourceFile())) {
@ -769,15 +772,15 @@ function isLocalCodeExtendingOrInheritingFromDefaultLibSymbol(program: ts.Progra
return false;
}
function findSymbolFromHeritageType(checker: ts.TypeChecker, type: ts.ExpressionWithTypeArguments | ts.Expression | ts.PrivateIdentifier): ts.Symbol | null {
function findSymbolFromHeritageType(ts: typeof import('typescript'), checker: ts.TypeChecker, type: ts.ExpressionWithTypeArguments | ts.Expression | ts.PrivateIdentifier): ts.Symbol | null {
if (ts.isExpressionWithTypeArguments(type)) {
return findSymbolFromHeritageType(checker, type.expression);
return findSymbolFromHeritageType(ts, checker, type.expression);
}
if (ts.isIdentifier(type)) {
return getRealNodeSymbol(checker, type)[0];
return getRealNodeSymbol(ts, checker, type)[0];
}
if (ts.isPropertyAccessExpression(type)) {
return findSymbolFromHeritageType(checker, type.name);
return findSymbolFromHeritageType(ts, checker, type.name);
}
return null;
}
@ -785,7 +788,7 @@ function findSymbolFromHeritageType(checker: ts.TypeChecker, type: ts.Expression
/**
* Returns the node's symbol and the `import` node (if the symbol resolved from a different module)
*/
function getRealNodeSymbol(checker: ts.TypeChecker, node: ts.Node): [ts.Symbol | null, ts.Declaration | null] {
function getRealNodeSymbol(ts: typeof import('typescript'), checker: ts.TypeChecker, node: ts.Node): [ts.Symbol | null, ts.Declaration | null] {
// Use some TypeScript internals to avoid code duplication
type ObjectLiteralElementWithName = ts.ObjectLiteralElement & { name: ts.PropertyName; parent: ts.ObjectLiteralExpression | ts.JsxAttributes };
@ -913,7 +916,7 @@ function getRealNodeSymbol(checker: ts.TypeChecker, node: ts.Node): [ts.Symbol |
}
/** Get the token whose text contains the position */
function getTokenAtPosition(sourceFile: ts.SourceFile, position: number, allowPositionInLeadingTrivia: boolean, includeEndPosition: boolean): ts.Node {
function getTokenAtPosition(ts: typeof import('typescript'), sourceFile: ts.SourceFile, position: number, allowPositionInLeadingTrivia: boolean, includeEndPosition: boolean): ts.Node {
let current: ts.Node = sourceFile;
outer: while (true) {
// find the child that contains 'position'

51
build/npm/dirs.js Normal file
View file

@ -0,0 +1,51 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// Complete list of directories where yarn should be executed to install node modules
exports.dirs = [
'',
'build',
'build/lib/watch',
'extensions',
'extensions/configuration-editing',
'extensions/css-language-features',
'extensions/css-language-features/server',
'extensions/debug-auto-launch',
'extensions/debug-server-ready',
'extensions/emmet',
'extensions/extension-editing',
'extensions/git',
'extensions/git-ui',
'extensions/github',
'extensions/github-authentication',
'extensions/grunt',
'extensions/gulp',
'extensions/html-language-features',
'extensions/html-language-features/server',
'extensions/image-preview',
'extensions/jake',
'extensions/json-language-features',
'extensions/json-language-features/server',
'extensions/markdown-language-features',
'extensions/merge-conflict',
'extensions/microsoft-authentication',
'extensions/npm',
'extensions/php-language-features',
'extensions/search-result',
'extensions/simple-browser',
'extensions/testing-editor-contributions',
'extensions/typescript-language-features',
'extensions/vscode-api-tests',
'extensions/vscode-colorize-tests',
'extensions/vscode-custom-editor-tests',
'extensions/vscode-notebook-tests',
'extensions/vscode-test-resolver',
'remote',
'remote/web',
'test/automation',
'test/integration/browser',
'test/monaco',
'test/smoke',
];

View file

@ -6,6 +6,7 @@
const cp = require('child_process');
const path = require('path');
const fs = require('fs');
const { dirs } = require('./dirs');
const yarn = process.platform === 'win32' ? 'yarn.cmd' : 'yarn';
/**
@ -35,29 +36,39 @@ function yarnInstall(location, opts) {
}
}
yarnInstall('extensions'); // node modules shared by all extensions
for (let dir of dirs) {
if (!(process.platform === 'win32' && (process.arch === 'arm64' || process.env['npm_config_arch'] === 'arm64'))) {
const env = { ...process.env };
if (process.env['VSCODE_REMOTE_CC']) { env['CC'] = process.env['VSCODE_REMOTE_CC']; }
if (process.env['VSCODE_REMOTE_CXX']) { env['CXX'] = process.env['VSCODE_REMOTE_CXX']; }
if (process.env['VSCODE_REMOTE_NODE_GYP']) { env['npm_config_node_gyp'] = process.env['VSCODE_REMOTE_NODE_GYP']; }
yarnInstall('remote', { env }); // node modules used by vscode server
yarnInstall('remote/web'); // node modules used by vscode web
}
const allExtensionFolders = fs.readdirSync('extensions');
const extensions = allExtensionFolders.filter(e => {
try {
let packageJSON = JSON.parse(fs.readFileSync(path.join('extensions', e, 'package.json')).toString());
return packageJSON && (packageJSON.dependencies || packageJSON.devDependencies);
} catch (e) {
return false;
if (dir === '') {
// `yarn` already executed in root
continue;
}
});
extensions.forEach(extension => yarnInstall(`extensions/${extension}`, { ignoreEngines: true }));
if (/^remote/.test(dir) && process.platform === 'win32' && (process.arch === 'arm64' || process.env['npm_config_arch'] === 'arm64')) {
// windows arm: do not execute `yarn` on remote folder
continue;
}
if (dir === 'build/lib/watch') {
// node modules for watching, specific to host node version, not electron
yarnInstallBuildDependencies();
continue;
}
let opts;
if (dir === 'remote') {
// node modules used by vscode server
const env = { ...process.env };
if (process.env['VSCODE_REMOTE_CC']) { env['CC'] = process.env['VSCODE_REMOTE_CC']; }
if (process.env['VSCODE_REMOTE_CXX']) { env['CXX'] = process.env['VSCODE_REMOTE_CXX']; }
if (process.env['VSCODE_REMOTE_NODE_GYP']) { env['npm_config_node_gyp'] = process.env['VSCODE_REMOTE_NODE_GYP']; }
opts = { env };
} else if (/^extensions\//.test(dir)) {
opts = { ignoreEngines: true };
}
yarnInstall(dir, opts);
}
function yarnInstallBuildDependencies() {
// make sure we install the deps of build/lib/watch for the system installed
@ -77,10 +88,4 @@ runtime "${runtime}"`;
yarnInstall(watchPath);
}
yarnInstall(`build`); // node modules required for build
yarnInstall('test/automation'); // node modules required for smoketest
yarnInstall('test/smoke'); // node modules required for smoketest
yarnInstall('test/integration/browser'); // node modules required for integration
yarnInstallBuildDependencies(); // node modules for watching, specific to host node version, not electron
cp.execSync('git config pull.rebase true');

View file

@ -17,7 +17,6 @@
"@types/gulp-json-editor": "^2.2.31",
"@types/gulp-rename": "^0.0.33",
"@types/gulp-sourcemaps": "^0.0.32",
"@types/gulp-uglify": "^3.0.5",
"@types/mime": "0.0.29",
"@types/minimatch": "^3.0.3",
"@types/minimist": "^1.2.1",
@ -26,7 +25,6 @@
"@types/pump": "^1.0.1",
"@types/request": "^2.47.0",
"@types/rimraf": "^2.0.4",
"@types/terser": "^3.12.0",
"@types/through": "^0.0.29",
"@types/through2": "^2.0.34",
"@types/underscore": "^1.8.9",
@ -36,28 +34,18 @@
"applicationinsights": "1.0.8",
"azure-storage": "^2.1.0",
"electron-osx-sign": "^0.4.16",
"github-releases": "^0.4.1",
"gulp-azure-storage": "^0.11.1",
"gulp-bom": "^1.0.0",
"gulp-gzip": "^1.4.2",
"gulp-sourcemaps": "^1.11.0",
"gulp-uglify": "^3.0.0",
"esbuild": "^0.8.30",
"iconv-lite-umd": "0.6.8",
"jsonc-parser": "^2.3.0",
"mime": "^1.4.1",
"minimatch": "^3.0.4",
"minimist": "^1.2.3",
"request": "^2.85.0",
"terser": "4.3.8",
"source-map": "0.6.1",
"typescript": "4.2.0-dev.20201207",
"vsce": "1.48.0",
"vscode-telemetry-extractor": "^1.6.0",
"xml2js": "^0.4.17"
"vsce": "1.48.0"
},
"scripts": {
"compile": "tsc -p tsconfig.build.json",
"watch": "tsc -p tsconfig.build.json --watch",
"postinstall": "npm run compile",
"npmCheckJs": "tsc --noEmit"
}
},
"dependencies": {}
}

File diff suppressed because it is too large Load diff

View file

@ -22,7 +22,6 @@
"compile": "gulp compile-extension:css-language-features-client compile-extension:css-language-features-server",
"watch": "gulp watch-extension:css-language-features-client watch-extension:css-language-features-server",
"test": "node ../../node_modules/mocha/bin/mocha",
"postinstall": "cd server && yarn install",
"install-client-next": "yarn add vscode-languageclient@next"
},
"categories": [

View file

@ -17,7 +17,7 @@
"url": "https://github.com/microsoft/vscode-emmet"
},
"activationEvents": [
"*",
"onStartupFinished",
"onCommand:emmet.expandAbbreviation",
"onLanguage:html",
"onLanguage:css",
@ -430,14 +430,16 @@
"deps": "yarn add vscode-emmet-helper"
},
"devDependencies": {
"@types/node": "^12.19.9"
"@types/node": "^12.19.9",
"emmet": "https://github.com/rzhao271/emmet.git#1b2df677d8925ef5ea6da9df8845968403979a0a"
},
"dependencies": {
"@emmetio/abbreviation": "^2.2.0",
"@emmetio/css-parser": "ramya-rao-a/css-parser#vscode",
"@emmetio/html-matcher": "^0.3.3",
"@emmetio/math-expression": "^1.0.4",
"image-size": "^0.5.2",
"vscode-emmet-helper": "~2.0.0",
"vscode-html-languageservice": "^3.0.3"
"vscode-emmet-helper": "2.2.4-2",
"vscode-languageserver-textdocument": "^1.0.1"
}
}

View file

@ -4,16 +4,19 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { Node, HtmlNode, Rule, Property, Stylesheet } from 'EmmetNode';
import { getEmmetHelper, getNode, getInnerRange, getMappingForIncludedLanguages, parseDocument, validate, getEmmetConfiguration, isStyleSheet, getEmmetMode, parsePartialStylesheet, isStyleAttribute, getEmbeddedCssNodeIfAny, allowedMimeTypesInScriptTag, toLSTextDocument } from './util';
import { Node, HtmlNode, Rule, Property, Stylesheet } from 'EmmetFlatNode';
import { getEmmetHelper, getFlatNode, getMappingForIncludedLanguages, validate, getEmmetConfiguration, isStyleSheet, getEmmetMode, parsePartialStylesheet, isStyleAttribute, getEmbeddedCssNodeIfAny, allowedMimeTypesInScriptTag, toLSTextDocument } from './util';
import { getRootNode as parseDocument } from './parseDocument';
import { MarkupAbbreviation } from 'emmet';
// import { AbbreviationNode } from '@emmetio/abbreviation';
const trimRegex = /[\u00a0]*[\d#\-\*\u2022]+\.?/;
const hexColorRegex = /^#[\da-fA-F]{0,6}$/;
const inlineElements = ['a', 'abbr', 'acronym', 'applet', 'b', 'basefont', 'bdo',
'big', 'br', 'button', 'cite', 'code', 'del', 'dfn', 'em', 'font', 'i',
'iframe', 'img', 'input', 'ins', 'kbd', 'label', 'map', 'object', 'q',
's', 'samp', 'select', 'small', 'span', 'strike', 'strong', 'sub', 'sup',
'textarea', 'tt', 'u', 'var'];
// const inlineElements = ['a', 'abbr', 'acronym', 'applet', 'b', 'basefont', 'bdo',
// 'big', 'br', 'button', 'cite', 'code', 'del', 'dfn', 'em', 'font', 'i',
// 'iframe', 'img', 'input', 'ins', 'kbd', 'label', 'map', 'object', 'q',
// 's', 'samp', 'select', 'small', 'span', 'strike', 'strong', 'sub', 'sup',
// 'textarea', 'tt', 'u', 'var'];
interface ExpandAbbreviationInput {
syntax: string;
@ -31,35 +34,35 @@ interface PreviewRangesWithContent {
}
export function wrapWithAbbreviation(args: any) {
return doWrapping(false, args);
return doWrapping(true, args);
}
export function wrapIndividualLinesWithAbbreviation(args: any) {
return doWrapping(true, args);
}
function doWrapping(individualLines: boolean, args: any) {
function doWrapping(_: boolean, args: any) {
if (!validate(false) || !vscode.window.activeTextEditor) {
return;
}
const editor = vscode.window.activeTextEditor;
if (individualLines) {
if (editor.selections.length === 1 && editor.selection.isEmpty) {
vscode.window.showInformationMessage('Select more than 1 line and try again.');
return;
}
if (editor.selections.find(x => x.isEmpty)) {
vscode.window.showInformationMessage('Select more than 1 line in each selection and try again.');
return;
}
}
// if (individualLines) {
// if (editor.selections.length === 1 && editor.selection.isEmpty) {
// vscode.window.showInformationMessage('Select more than 1 line and try again.');
// return;
// }
// if (editor.selections.find(x => x.isEmpty)) {
// vscode.window.showInformationMessage('Select more than 1 line in each selection and try again.');
// return;
// }
// }
args = args || {};
if (!args['language']) {
args['language'] = editor.document.languageId;
}
const syntax = getSyntaxFromArgs(args) || 'html';
const rootNode = parseDocument(editor.document, false);
const rootNode = parseDocument(editor.document, true);
let inPreview = false;
let currentValue = '';
@ -68,35 +71,45 @@ function doWrapping(individualLines: boolean, args: any) {
// Fetch general information for the succesive expansions. i.e. the ranges to replace and its contents
const rangesToReplace: PreviewRangesWithContent[] = editor.selections.sort((a: vscode.Selection, b: vscode.Selection) => { return a.start.compareTo(b.start); }).map(selection => {
let rangeToReplace: vscode.Range = selection.isReversed ? new vscode.Range(selection.active, selection.anchor) : selection;
const document = editor.document;
if (!rangeToReplace.isSingleLine && rangeToReplace.end.character === 0) {
const previousLine = rangeToReplace.end.line - 1;
const lastChar = editor.document.lineAt(previousLine).text.length;
const lastChar = document.lineAt(previousLine).text.length;
rangeToReplace = new vscode.Range(rangeToReplace.start, new vscode.Position(previousLine, lastChar));
} else if (rangeToReplace.isEmpty) {
const { active } = selection;
const currentNode = getNode(rootNode, active, true);
if (currentNode && (currentNode.start.line === active.line || currentNode.end.line === active.line)) {
rangeToReplace = new vscode.Range(currentNode.start, currentNode.end);
const activeOffset = document.offsetAt(active);
const currentNode = getFlatNode(rootNode, activeOffset, true);
if (currentNode) {
const currentNodeStart = document.positionAt(currentNode.start);
const currentNodeEnd = document.positionAt(currentNode.end);
if (currentNodeStart.line === active.line || currentNodeEnd.line === active.line) {
rangeToReplace = new vscode.Range(currentNodeStart, currentNodeEnd);
}
else {
rangeToReplace = new vscode.Range(rangeToReplace.start.line, 0, rangeToReplace.start.line, document.lineAt(rangeToReplace.start.line).text.length);
}
} else {
rangeToReplace = new vscode.Range(rangeToReplace.start.line, 0, rangeToReplace.start.line, editor.document.lineAt(rangeToReplace.start.line).text.length);
rangeToReplace = new vscode.Range(rangeToReplace.start.line, 0, rangeToReplace.start.line, document.lineAt(rangeToReplace.start.line).text.length);
}
}
const firstLineOfSelection = editor.document.lineAt(rangeToReplace.start).text.substr(rangeToReplace.start.character);
const firstLineOfSelection = document.lineAt(rangeToReplace.start).text.substr(rangeToReplace.start.character);
const matches = firstLineOfSelection.match(/^(\s*)/);
const extraWhitespaceSelected = matches ? matches[1].length : 0;
rangeToReplace = new vscode.Range(rangeToReplace.start.line, rangeToReplace.start.character + extraWhitespaceSelected, rangeToReplace.end.line, rangeToReplace.end.character);
let textToWrapInPreview: string[];
const textToReplace = editor.document.getText(rangeToReplace);
if (individualLines) {
textToWrapInPreview = textToReplace.split('\n').map(x => x.trim());
} else {
const wholeFirstLine = editor.document.lineAt(rangeToReplace.start).text;
const otherMatches = wholeFirstLine.match(/^(\s*)/);
const precedingWhitespace = otherMatches ? otherMatches[1] : '';
textToWrapInPreview = rangeToReplace.isSingleLine ? [textToReplace] : ['\n\t' + textToReplace.split('\n' + precedingWhitespace).join('\n\t') + '\n'];
}
const textToReplace = document.getText(rangeToReplace);
// if (individualLines) {
// textToWrapInPreview = textToReplace.split('\n').map(x => x.trim());
// } else {
// the following assumes the lines are indented the same way
const wholeFirstLine = document.lineAt(rangeToReplace.start).text;
const otherMatches = wholeFirstLine.match(/^(\s*)/);
const precedingWhitespace = otherMatches ? otherMatches[1] : '';
textToWrapInPreview = rangeToReplace.isSingleLine ? [textToReplace] : textToReplace.split('\n' + precedingWhitespace).map(x => x.trimEnd());
// }
textToWrapInPreview = textToWrapInPreview.map(e => e.replace(/(\$\d)/g, '\\$1'));
return {
@ -107,7 +120,7 @@ function doWrapping(individualLines: boolean, args: any) {
};
});
function revertPreview(): Thenable<any> {
function revertPreview(): Thenable<boolean> {
return editor.edit(builder => {
for (const rangeToReplace of rangesToReplace) {
builder.replace(rangeToReplace.previewRange, rangeToReplace.originalContent);
@ -133,7 +146,8 @@ function doWrapping(individualLines: boolean, args: any) {
const preceedingText = editor.document.getText(new vscode.Range(oldPreviewRange.start.line, 0, oldPreviewRange.start.line, oldPreviewRange.start.character));
const indentPrefix = (preceedingText.match(/^(\s*)/) || ['', ''])[1];
let newText = expandedText.replace(/\n/g, '\n' + indentPrefix); // Adding indentation on each line of expanded text
let newText = expandedText;
newText = newText.replace(/\n/g, '\n' + indentPrefix); // Adding indentation on each line of expanded text
newText = newText.replace(/\$\{[\d]*\}/g, '|'); // Removing Tabstops
newText = newText.replace(/\$\{[\d]*(:[^}]*)?\}/g, (match) => { // Replacing Placeholders
return match.replace(/^\$\{[\d]*:/, '').replace('}', '');
@ -187,19 +201,21 @@ function doWrapping(individualLines: boolean, args: any) {
const { abbreviation, filter } = extractedResults;
if (definitive) {
const revertPromise = inPreview ? revertPreview() : Promise.resolve();
const revertPromise = inPreview ? revertPreview() : Promise.resolve(true);
return revertPromise.then(() => {
const expandAbbrList: ExpandAbbreviationInput[] = rangesToReplace.map(rangesAndContent => {
const rangeToReplace = rangesAndContent.originalRange;
let textToWrap: string[];
if (individualLines) {
textToWrap = rangesAndContent.textToWrapInPreview;
} else {
textToWrap = rangeToReplace.isSingleLine ? ['$TM_SELECTED_TEXT'] : ['\n\t$TM_SELECTED_TEXT\n'];
}
// if (individualLines) {
textToWrap = rangesAndContent.textToWrapInPreview;
// } else {
// // use the p tag as a dummy element to get Emmet to wrap the expression properly
// textToWrap = rangeToReplace.isSingleLine ?
// ['$TM_SELECTED_TEXT'] : ['<p>$TM_SELECTED_TEXT</p>'];
// }
return { syntax: syntax || '', abbreviation, rangeToReplace, textToWrap, filter };
});
return expandAbbreviationInRange(editor, expandAbbrList, !individualLines).then(() => { return true; });
return expandAbbreviationInRange(editor, expandAbbrList, false).then(() => { return true; });
});
}
@ -214,14 +230,15 @@ function doWrapping(individualLines: boolean, args: any) {
if (value !== currentValue) {
currentValue = value;
makeChanges(value, false).then((out) => {
if (typeof out === 'boolean') {
inPreview = out;
}
inPreview = out;
});
}
return '';
}
const abbreviationPromise: Thenable<string | undefined> = (args && args['abbreviation']) ? Promise.resolve(args['abbreviation']) : vscode.window.showInputBox({ prompt: 'Enter Abbreviation', validateInput: inputChanged });
const abbreviationPromise: Thenable<string | undefined> = (args && args['abbreviation']) ?
Promise.resolve(args['abbreviation']) :
vscode.window.showInputBox({ prompt: 'Enter Abbreviation', validateInput: inputChanged });
return abbreviationPromise.then(inputAbbreviation => {
return makeChanges(inputAbbreviation, true);
});
@ -327,7 +344,7 @@ export function expandEmmetAbbreviation(args: any): Thenable<boolean | undefined
if (editor.selections.length === 1 && isStyleSheet(editor.document.languageId) && usePartialParsing && editor.document.lineCount > 1000) {
rootNode = parsePartialStylesheet(editor.document, editor.selection.isReversed ? editor.selection.anchor : editor.selection.active);
} else {
rootNode = parseDocument(editor.document, false);
rootNode = parseDocument(editor.document, true);
}
return rootNode;
@ -342,24 +359,25 @@ export function expandEmmetAbbreviation(args: any): Thenable<boolean | undefined
if (!helper.isAbbreviationValid(syntax, abbreviation)) {
return;
}
let currentNode = getNode(getRootNode(), position, true);
const offset = editor.document.offsetAt(position);
let currentNode = getFlatNode(getRootNode(), offset, true);
let validateLocation = true;
let syntaxToUse = syntax;
if (editor.document.languageId === 'html') {
if (isStyleAttribute(currentNode, position)) {
if (isStyleAttribute(currentNode, offset)) {
syntaxToUse = 'css';
validateLocation = false;
} else {
const embeddedCssNode = getEmbeddedCssNodeIfAny(editor.document, currentNode, position);
if (embeddedCssNode) {
currentNode = getNode(embeddedCssNode, position, true);
currentNode = getFlatNode(embeddedCssNode, offset, true);
syntaxToUse = 'css';
}
}
}
if (validateLocation && !isValidLocationForEmmetAbbreviation(editor.document, getRootNode(), currentNode, syntaxToUse, position, rangeToReplace)) {
if (validateLocation && !isValidLocationForEmmetAbbreviation(editor.document, getRootNode(), currentNode, syntaxToUse, offset, rangeToReplace)) {
return;
}
@ -393,10 +411,10 @@ function fallbackTab(): Thenable<boolean | undefined> {
* @param position position to validate
* @param abbreviationRange The range of the abbreviation for which given position is being validated
*/
export function isValidLocationForEmmetAbbreviation(document: vscode.TextDocument, rootNode: Node | undefined, currentNode: Node | null, syntax: string, position: vscode.Position, abbreviationRange: vscode.Range): boolean {
export function isValidLocationForEmmetAbbreviation(document: vscode.TextDocument, rootNode: Node | undefined, currentNode: Node | undefined, syntax: string, offset: number, abbreviationRange: vscode.Range): boolean {
if (isStyleSheet(syntax)) {
const stylesheet = <Stylesheet>rootNode;
if (stylesheet && (stylesheet.comments || []).some(x => position.isAfterOrEqual(x.start) && position.isBeforeOrEqual(x.end))) {
if (stylesheet && (stylesheet.comments || []).some(x => offset >= x.start && offset <= x.end)) {
return false;
}
// Continue validation only if the file was parse-able and the currentNode has been found
@ -419,14 +437,14 @@ export function isValidLocationForEmmetAbbreviation(document: vscode.TextDocumen
const propertyNode = <Property>currentNode;
if (propertyNode.terminatorToken
&& propertyNode.separator
&& position.isAfterOrEqual(propertyNode.separatorToken.end)
&& position.isBeforeOrEqual(propertyNode.terminatorToken.start)
&& offset >= propertyNode.separatorToken.end
&& offset <= propertyNode.terminatorToken.start
&& abbreviation.indexOf(':') === -1) {
return hexColorRegex.test(abbreviation) || abbreviation === '!';
}
if (!propertyNode.terminatorToken
&& propertyNode.separator
&& position.isAfterOrEqual(propertyNode.separatorToken.end)
&& offset >= propertyNode.separatorToken.end
&& abbreviation.indexOf(':') === -1) {
return hexColorRegex.test(abbreviation) || abbreviation === '!';
}
@ -444,7 +462,7 @@ export function isValidLocationForEmmetAbbreviation(document: vscode.TextDocumen
const currentCssNode = <Rule>currentNode;
// Position is valid if it occurs after the `{` that marks beginning of rule contents
if (position.isAfter(currentCssNode.contentStartToken.end)) {
if (offset > currentCssNode.contentStartToken.end) {
return true;
}
@ -453,12 +471,16 @@ export function isValidLocationForEmmetAbbreviation(document: vscode.TextDocumen
// But we should assume it is a valid location for css properties under the parent rule
if (currentCssNode.parent
&& (currentCssNode.parent.type === 'rule' || currentCssNode.parent.type === 'at-rule')
&& currentCssNode.selectorToken
&& position.line !== currentCssNode.selectorToken.end.line
&& currentCssNode.selectorToken.start.character === abbreviationRange.start.character
&& currentCssNode.selectorToken.start.line === abbreviationRange.start.line
) {
return true;
&& currentCssNode.selectorToken) {
const position = document.positionAt(offset);
const tokenStartPos = document.positionAt(currentCssNode.selectorToken.start);
const tokenEndPos = document.positionAt(currentCssNode.selectorToken.end);
if (position.line !== tokenEndPos.line
&& tokenStartPos.character === abbreviationRange.start.character
&& tokenStartPos.line === abbreviationRange.start.line
) {
return true;
}
}
return false;
@ -469,7 +491,7 @@ export function isValidLocationForEmmetAbbreviation(document: vscode.TextDocumen
const escape = '\\';
const question = '?';
const currentHtmlNode = <HtmlNode>currentNode;
let start = new vscode.Position(0, 0);
let start = 0;
if (currentHtmlNode) {
if (currentHtmlNode.name === 'script') {
@ -487,27 +509,27 @@ export function isValidLocationForEmmetAbbreviation(document: vscode.TextDocumen
return false;
}
const innerRange = getInnerRange(currentHtmlNode);
// Fix for https://github.com/microsoft/vscode/issues/28829
if (!innerRange || !innerRange.contains(position)) {
if (!currentHtmlNode.open || !currentHtmlNode.close ||
!(currentHtmlNode.open.end <= offset && offset <= currentHtmlNode.close.start)) {
return false;
}
// Fix for https://github.com/microsoft/vscode/issues/35128
// Find the position up till where we will backtrack looking for unescaped < or >
// to decide if current position is valid for emmet expansion
start = innerRange.start;
start = currentHtmlNode.open.end;
let lastChildBeforePosition = currentHtmlNode.firstChild;
while (lastChildBeforePosition) {
if (lastChildBeforePosition.end.isAfter(position)) {
if (lastChildBeforePosition.end > offset) {
break;
}
start = lastChildBeforePosition.end;
lastChildBeforePosition = lastChildBeforePosition.nextSibling;
}
}
let textToBackTrack = document.getText(new vscode.Range(start.line, start.character, abbreviationRange.start.line, abbreviationRange.start.character));
const startPos = document.positionAt(start);
let textToBackTrack = document.getText(new vscode.Range(startPos.line, startPos.character, abbreviationRange.start.line, abbreviationRange.start.character));
// Worse case scenario is when cursor is inside a big chunk of text which needs to backtracked
// Backtrack only 500 offsets to ensure we dont waste time doing this
@ -582,7 +604,7 @@ function expandAbbreviationInRange(editor: vscode.TextEditor, expandAbbrList: Ex
const insertPromises: Thenable<boolean>[] = [];
if (!insertSameSnippet) {
expandAbbrList.sort((a: ExpandAbbreviationInput, b: ExpandAbbreviationInput) => { return b.rangeToReplace.start.compareTo(a.rangeToReplace.start); }).forEach((expandAbbrInput: ExpandAbbreviationInput) => {
let expandedText = expandAbbr(expandAbbrInput);
const expandedText = expandAbbr(expandAbbrInput);
if (expandedText) {
insertPromises.push(editor.insertSnippet(new vscode.SnippetString(expandedText), expandAbbrInput.rangeToReplace, { undoStopBefore: false, undoStopAfter: false }));
}
@ -607,24 +629,26 @@ function expandAbbreviationInRange(editor: vscode.TextEditor, expandAbbrList: Ex
return Promise.resolve(false);
}
/*
* Walks the tree rooted at root and apply function fn on each node.
* if fn return false at any node, the further processing of tree is stopped.
*/
function walk(root: any, fn: ((node: any) => boolean)): boolean {
let ctx = root;
while (ctx) {
// /*
// * Walks the tree rooted at root and apply function fn on each node.
// * if fn return false at any node, the further processing of tree is stopped.
// */
// function walk(root: AbbreviationNode, fn: ((node: AbbreviationNode) => boolean)): boolean {
// if (fn(root) === false || walkChildren(root.children, fn) === false) {
// return false;
// }
// return true;
// }
const next = ctx.next;
if (fn(ctx) === false || walk(ctx.firstChild, fn) === false) {
return false;
}
ctx = next;
}
return true;
}
// function walkChildren(children: AbbreviationNode[], fn: ((node: AbbreviationNode) => boolean)): boolean {
// for (let i = 0; i < children.length; i++) {
// const child = children[i];
// if (walk(child, fn) === false) {
// return false;
// }
// }
// return true;
// }
/**
* Expands abbreviation as detailed in given input.
@ -634,7 +658,7 @@ function expandAbbr(input: ExpandAbbreviationInput): string | undefined {
const expandOptions = helper.getExpandOptions(input.syntax, getEmmetConfiguration(input.syntax), input.filter);
if (input.textToWrap) {
if (input.filter && input.filter.indexOf('t') > -1) {
if (input.filter && input.filter.includes('t')) {
input.textToWrap = input.textToWrap.map(line => {
return line.replace(trimRegex, '').trim();
});
@ -644,46 +668,47 @@ function expandAbbr(input: ExpandAbbreviationInput): string | undefined {
// Below fixes https://github.com/microsoft/vscode/issues/29898
// With this, Emmet formats inline elements as block elements
// ensuring the wrapped multi line text does not get merged to a single line
if (!input.rangeToReplace.isSingleLine) {
expandOptions.profile['inlineBreak'] = 1;
if (!input.rangeToReplace.isSingleLine && expandOptions.options) {
expandOptions.options['output.inlineBreak'] = 1;
}
}
let expandedText;
try {
// Expand the abbreviation
if (input.textToWrap && !isStyleSheet(input.syntax)) {
const parsedAbbr = <MarkupAbbreviation>helper.parseAbbreviation(input.abbreviation, expandOptions);
// if (input.rangeToReplace.isSingleLine && input.textToWrap.length === 1) {
// // Fetch rightmost element in the parsed abbreviation (i.e the element that will contain the wrapped text).
// const wrappingNodeChildren = parsedAbbr.children;
// let wrappingNode = wrappingNodeChildren[wrappingNodeChildren.length - 1];
// while (wrappingNode && wrappingNode.children && wrappingNode.children.length > 0) {
// wrappingNode = wrappingNode.children[wrappingNode.children.length - 1];
// }
if (input.textToWrap) {
const parsedAbbr = helper.parseAbbreviation(input.abbreviation, expandOptions);
if (input.rangeToReplace.isSingleLine && input.textToWrap.length === 1) {
// Fetch rightmost element in the parsed abbreviation (i.e the element that will contain the wrapped text).
let wrappingNode = parsedAbbr;
while (wrappingNode && wrappingNode.children && wrappingNode.children.length > 0) {
wrappingNode = wrappingNode.children[wrappingNode.children.length - 1];
}
// If wrapping with a block element, insert newline in the text to wrap.
if (wrappingNode && inlineElements.indexOf(wrappingNode.name) === -1 && (expandOptions['profile'].hasOwnProperty('format') ? expandOptions['profile'].format : true)) {
wrappingNode.value = '\n\t' + wrappingNode.value + '\n';
}
}
// // If wrapping with a block element, insert newline in the text to wrap.
// // const format = expandOptions.options ? (expandOptions.options['output.format'] ?? true) : true;
// // if (wrappingNode && wrappingNode.name && wrappingNode.value
// // && inlineElements.indexOf(wrappingNode.name) === -1
// // && format) {
// // wrappingNode.value[0] = '\n\t' + wrappingNode.value[0] + '\n';
// // }
// }
// Below fixes https://github.com/microsoft/vscode/issues/78219
// walk the tree and remove tags for empty values
walk(parsedAbbr, node => {
if (node.name !== null && node.value === '' && !node.isSelfClosing && node.children.length === 0) {
node.name = '';
node.value = '\n';
}
return true;
});
// walkChildren(parsedAbbr.children, node => {
// if (node.name !== null && node.value && node.value[0] === '' && !node.selfClosing && node.children.length === 0) {
// node.name = '';
// node.value[0] = '\n';
// }
// return true;
// });
expandedText = helper.expandAbbreviation(parsedAbbr, expandOptions);
// All $anyword would have been escaped by the emmet helper.
// Remove the escaping backslash from $TM_SELECTED_TEXT so that VS Code Snippet controller can treat it as a variable
expandedText = expandedText.replace('\\$TM_SELECTED_TEXT', '$TM_SELECTED_TEXT');
expandedText = expandedText.replace('<p>\\$TM_SELECTED_TEXT</p>', '$TM_SELECTED_TEXT');
} else {
expandedText = helper.expandAbbreviation(input.abbreviation, expandOptions);
}

View file

@ -4,9 +4,9 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { getHtmlNodeLS, offsetRangeToSelection, toLSTextDocument, validate } from './util';
import { parseMarkupDocument } from './parseMarkupDocument';
import { TextDocument as LSTextDocument } from 'vscode-html-languageservice';
import { getHtmlFlatNode, offsetRangeToSelection, validate } from './util';
import { getRootNode } from './parseDocument';
import { HtmlNode as HtmlFlatNode } from 'EmmetFlatNode';
let balanceOutStack: Array<vscode.Selection[]> = [];
let lastBalancedSelections: vscode.Selection[] = [];
@ -24,16 +24,16 @@ function balance(out: boolean) {
return;
}
const editor = vscode.window.activeTextEditor;
const document = toLSTextDocument(editor.document);
const htmlDocument = parseMarkupDocument(document);
if (!htmlDocument) {
const document = editor.document;
const rootNode = <HtmlFlatNode>getRootNode(document, true);
if (!rootNode) {
return;
}
const rangeFn = out ? getRangeToBalanceOut : getRangeToBalanceIn;
let newSelections: vscode.Selection[] = [];
editor.selections.forEach(selection => {
const range = rangeFn(document, selection);
const range = rangeFn(document, rootNode, selection);
newSelections.push(range);
});
@ -57,17 +57,18 @@ function balance(out: boolean) {
lastBalancedSelections = editor.selections;
}
function getRangeToBalanceOut(document: LSTextDocument, selection: vscode.Selection): vscode.Selection {
const nodeToBalance = getHtmlNodeLS(document, selection.start, false);
function getRangeToBalanceOut(document: vscode.TextDocument, rootNode: HtmlFlatNode, selection: vscode.Selection): vscode.Selection {
const offset = document.offsetAt(selection.start);
const nodeToBalance = getHtmlFlatNode(document.getText(), rootNode, offset, false);
if (!nodeToBalance) {
return selection;
}
if (!nodeToBalance.endTagStart || !nodeToBalance.startTagEnd) {
if (!nodeToBalance.open || !nodeToBalance.close) {
return offsetRangeToSelection(document, nodeToBalance.start, nodeToBalance.end);
}
const innerSelection = offsetRangeToSelection(document, nodeToBalance.startTagEnd, nodeToBalance.endTagStart);
const outerSelection = offsetRangeToSelection(document, nodeToBalance.start, nodeToBalance.end);
const innerSelection = offsetRangeToSelection(document, nodeToBalance.open.end, nodeToBalance.close.start);
const outerSelection = offsetRangeToSelection(document, nodeToBalance.open.start, nodeToBalance.close.end);
if (innerSelection.contains(selection) && !innerSelection.isEqual(selection)) {
return innerSelection;
@ -78,34 +79,35 @@ function getRangeToBalanceOut(document: LSTextDocument, selection: vscode.Select
return selection;
}
function getRangeToBalanceIn(document: LSTextDocument, selection: vscode.Selection): vscode.Selection {
const nodeToBalance = getHtmlNodeLS(document, selection.start, true);
function getRangeToBalanceIn(document: vscode.TextDocument, rootNode: HtmlFlatNode, selection: vscode.Selection): vscode.Selection {
const offset = document.offsetAt(selection.start);
const nodeToBalance = getHtmlFlatNode(document.getText(), rootNode, offset, true);
if (!nodeToBalance) {
return selection;
}
const selectionStart = document.offsetAt(selection.start);
const selectionEnd = document.offsetAt(selection.end);
if (nodeToBalance.endTagStart !== undefined && nodeToBalance.startTagEnd !== undefined) {
if (nodeToBalance.open && nodeToBalance.close) {
const entireNodeSelected = selectionStart === nodeToBalance.start && selectionEnd === nodeToBalance.end;
const startInOpenTag = selectionStart > nodeToBalance.start && selectionStart < nodeToBalance.startTagEnd;
const startInCloseTag = selectionStart > nodeToBalance.endTagStart && selectionStart < nodeToBalance.end;
const startInOpenTag = selectionStart > nodeToBalance.open.start && selectionStart < nodeToBalance.open.end;
const startInCloseTag = selectionStart > nodeToBalance.close.start && selectionStart < nodeToBalance.close.end;
if (entireNodeSelected || startInOpenTag || startInCloseTag) {
return offsetRangeToSelection(document, nodeToBalance.startTagEnd, nodeToBalance.endTagStart);
return offsetRangeToSelection(document, nodeToBalance.open.end, nodeToBalance.close.start);
}
}
if (!nodeToBalance.children.length) {
if (!nodeToBalance.firstChild) {
return selection;
}
const firstChild = nodeToBalance.children[0];
const firstChild = nodeToBalance.firstChild;
if (selectionStart === firstChild.start
&& selectionEnd === firstChild.end
&& firstChild.endTagStart !== undefined
&& firstChild.startTagEnd !== undefined) {
return offsetRangeToSelection(document, firstChild.startTagEnd, firstChild.endTagStart);
&& firstChild.open
&& firstChild.close) {
return offsetRangeToSelection(document, firstChild.open.end, firstChild.close.start);
}
return offsetRangeToSelection(document, firstChild.start, firstChild.end);

View file

@ -5,7 +5,7 @@
/* Based on @sergeche's work in his emmet plugin */
import { TextDocument, Position, Range, EndOfLine } from 'vscode';
import { TextDocument } from 'vscode';
/**
* A stream reader for VSCode's `TextDocument`
@ -13,40 +13,37 @@ import { TextDocument, Position, Range, EndOfLine } from 'vscode';
*/
export class DocumentStreamReader {
private document: TextDocument;
private start: Position;
private _eof: Position;
private _sof: Position;
public pos: Position;
private _eol: string;
constructor(document: TextDocument, pos?: Position, limit?: Range) {
private start: number;
private _eof: number;
private _sof: number;
public pos: number;
constructor(document: TextDocument, pos?: number, limit?: [number, number]) {
this.document = document;
this.start = this.pos = pos ? pos : new Position(0, 0);
this._sof = limit ? limit.start : new Position(0, 0);
this._eof = limit ? limit.end : new Position(this.document.lineCount - 1, this._lineLength(this.document.lineCount - 1));
this._eol = this.document.eol === EndOfLine.LF ? '\n' : '\r\n';
this.start = this.pos = pos ? pos : 0;
this._sof = limit ? limit[0] : 0;
this._eof = limit ? limit[1] : document.getText().length;
}
/**
* Returns true only if the stream is at the start of the file.
*/
sof(): boolean {
return this.pos.isBeforeOrEqual(this._sof);
return this.pos <= this._sof;
}
/**
* Returns true only if the stream is at the end of the file.
*/
eof(): boolean {
return this.pos.isAfterOrEqual(this._eof);
return this.pos >= this._eof;
}
/**
* Creates a new stream instance which is limited to given range for given document
*/
limit(start: Position, end: Position): DocumentStreamReader {
return new DocumentStreamReader(this.document, start, new Range(start, end));
limit(start: number, end: number): DocumentStreamReader {
return new DocumentStreamReader(this.document, start, [start, end]);
}
/**
@ -57,8 +54,7 @@ export class DocumentStreamReader {
if (this.eof()) {
return NaN;
}
const line = this.document.lineAt(this.pos.line).text;
return this.pos.character < line.length ? line.charCodeAt(this.pos.character) : this._eol.charCodeAt(this.pos.character - line.length);
return this.document.getText().charCodeAt(this.pos);
}
/**
@ -70,19 +66,12 @@ export class DocumentStreamReader {
return NaN;
}
const line = this.document.lineAt(this.pos.line).text;
let code: number;
if (this.pos.character < line.length) {
code = line.charCodeAt(this.pos.character);
this.pos = this.pos.translate(0, 1);
} else {
code = this._eol.charCodeAt(this.pos.character - line.length);
this.pos = new Position(this.pos.line + 1, 0);
}
const code = this.document.getText().charCodeAt(this.pos);
this.pos++;
if (this.eof()) {
// restrict pos to eof, if in case it got moved beyond eof
this.pos = new Position(this._eof.line, this._eof.character);
this.pos = this._eof;
}
return code;
@ -92,20 +81,11 @@ export class DocumentStreamReader {
* Backs up the stream n characters. Backing it up further than the
* start of the current token will cause things to break, so be careful.
*/
backUp(n: number) {
let row = this.pos.line;
let column = this.pos.character;
column -= (n || 1);
while (row >= 0 && column < 0) {
row--;
column += this._lineLength(row);
backUp(n: number): number {
this.pos -= n;
if (this.pos < 0) {
this.pos = 0;
}
this.pos = row < 0 || column < 0
? new Position(0, 0)
: new Position(row, column);
return this.peek();
}
@ -120,29 +100,18 @@ export class DocumentStreamReader {
/**
* Returns contents for given range
*/
substring(from: Position, to: Position): string {
return this.document.getText(new Range(from, to));
substring(from: number, to: number): string {
return this.document.getText().substring(from, to);
}
/**
* Creates error object with current stream state
*/
error(message: string): Error {
const err = new Error(`${message} at row ${this.pos.line}, column ${this.pos.character}`);
const err = new Error(`${message} at offset ${this.pos}`);
return err;
}
/**
* Returns line length of given row, including line ending
*/
_lineLength(row: number): number {
if (row === this.document.lineCount - 1) {
return this.document.lineAt(row).text.length;
}
return this.document.lineAt(row).text.length + this._eol.length;
}
/**
* `match` can be a character code or a function that takes a character code
* and returns a boolean. If the next character in the stream 'matches'
@ -167,6 +136,6 @@ export class DocumentStreamReader {
eatWhile(match: number | Function): boolean {
const start = this.pos;
while (!this.eof() && this.eat(match)) { }
return !this.pos.isEqual(start);
return this.pos !== start;
}
}

View file

@ -4,17 +4,16 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { Node, Stylesheet } from 'EmmetNode';
import { Node, Stylesheet } from 'EmmetFlatNode';
import { isValidLocationForEmmetAbbreviation, getSyntaxFromArgs } from './abbreviationActions';
import { getEmmetHelper, getMappingForIncludedLanguages, parsePartialStylesheet, getEmmetConfiguration, getEmmetMode, isStyleSheet, parseDocument, getNode, allowedMimeTypesInScriptTag, trimQuotes, toLSTextDocument } from './util';
import { getLanguageService, TokenType, Range as LSRange } from 'vscode-html-languageservice';
import { getEmmetHelper, getMappingForIncludedLanguages, parsePartialStylesheet, getEmmetConfiguration, getEmmetMode, isStyleSheet, getFlatNode, allowedMimeTypesInScriptTag, toLSTextDocument, getHtmlFlatNode } from './util';
import { Range as LSRange } from 'vscode-languageserver-textdocument';
import { getRootNode } from './parseDocument';
export class DefaultCompletionItemProvider implements vscode.CompletionItemProvider {
private lastCompletionType: string | undefined;
private htmlLS = getLanguageService();
public provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, _: vscode.CancellationToken, context: vscode.CompletionContext): Thenable<vscode.CompletionList | undefined> | undefined {
const completionResult = this.provideCompletionItemsInternal(document, position, context);
if (!completionResult) {
@ -62,8 +61,8 @@ export class DefaultCompletionItemProvider implements vscode.CompletionItemProvi
const helper = getEmmetHelper();
let validateLocation = syntax === 'html' || syntax === 'jsx' || syntax === 'xml';
let rootNode: Node | undefined = undefined;
let currentNode: Node | null = null;
let rootNode: Node | undefined;
let currentNode: Node | undefined;
const lsDoc = toLSTextDocument(document);
position = document.validatePosition(position);
@ -81,19 +80,16 @@ export class DefaultCompletionItemProvider implements vscode.CompletionItemProvi
default:
break;
}
}
if (validateLocation) {
const parsedLsDoc = this.htmlLS.parseHTMLDocument(lsDoc);
const positionOffset = document.offsetAt(position);
const node = parsedLsDoc.findNodeAt(positionOffset);
if (node.tag === 'script') {
if (node.attributes && 'type' in node.attributes) {
const rawTypeAttrValue = node.attributes['type'];
if (rawTypeAttrValue) {
const typeAttrValue = trimQuotes(rawTypeAttrValue);
const emmetRootNode = getRootNode(document, true);
const foundNode = getHtmlFlatNode(document.getText(), emmetRootNode, positionOffset, false);
if (foundNode) {
if (foundNode.name === 'script') {
const typeNode = foundNode.attributes.find(attr => attr.name.toString() === 'type');
if (typeNode) {
const typeAttrValue = typeNode.value.toString();
if (typeAttrValue === 'application/javascript' || typeAttrValue === 'text/javascript') {
if (!getSyntaxFromArgs({ language: 'javascript' })) {
return;
@ -101,34 +97,19 @@ export class DefaultCompletionItemProvider implements vscode.CompletionItemProvi
validateLocation = false;
}
}
else if (allowedMimeTypesInScriptTag.indexOf(trimQuotes(rawTypeAttrValue)) > -1) {
else if (allowedMimeTypesInScriptTag.includes(typeAttrValue)) {
validateLocation = false;
}
} else {
return;
}
} else {
return;
}
}
else if (node.tag === 'style') {
syntax = 'css';
validateLocation = false;
} else {
if (node.attributes && node.attributes['style']) {
const scanner = this.htmlLS.createScanner(document.getText(), node.start);
let tokenType = scanner.scan();
let prevAttr = undefined;
let styleAttrValueRange: [number, number] | undefined = undefined;
while (tokenType !== TokenType.EOS && (scanner.getTokenEnd() <= positionOffset)) {
tokenType = scanner.scan();
if (tokenType === TokenType.AttributeName) {
prevAttr = scanner.getTokenText();
}
else if (tokenType === TokenType.AttributeValue && prevAttr === 'style') {
styleAttrValueRange = [scanner.getTokenOffset(), scanner.getTokenEnd()];
}
}
if (prevAttr === 'style' && styleAttrValueRange && positionOffset > styleAttrValueRange[0] && positionOffset < styleAttrValueRange[1]) {
else if (foundNode.name === 'style') {
syntax = 'css';
validateLocation = false;
} else {
const styleNode = foundNode.attributes.find(attr => attr.name.toString() === 'style');
if (styleNode && styleNode.value.start <= positionOffset && positionOffset <= styleNode.value.end) {
syntax = 'css';
validateLocation = false;
}
@ -145,19 +126,18 @@ export class DefaultCompletionItemProvider implements vscode.CompletionItemProvi
return;
}
const offset = document.offsetAt(position);
if (isStyleSheet(document.languageId) && context.triggerKind !== vscode.CompletionTriggerKind.TriggerForIncompleteCompletions) {
validateLocation = true;
let usePartialParsing = vscode.workspace.getConfiguration('emmet')['optimizeStylesheetParsing'] === true;
rootNode = usePartialParsing && document.lineCount > 1000 ? parsePartialStylesheet(document, position) : <Stylesheet>parseDocument(document, false);
rootNode = usePartialParsing && document.lineCount > 1000 ? parsePartialStylesheet(document, position) : <Stylesheet>getRootNode(document, true);
if (!rootNode) {
return;
}
currentNode = getNode(rootNode, position, true);
currentNode = getFlatNode(rootNode, offset, true);
}
if (validateLocation && !isValidLocationForEmmetAbbreviation(document, rootNode, currentNode, syntax, position, toRange(extractAbbreviationResults.abbreviationRange))) {
if (validateLocation && !isValidLocationForEmmetAbbreviation(document, rootNode, currentNode, syntax, offset, toRange(extractAbbreviationResults.abbreviationRange))) {
return;
}

View file

@ -17,9 +17,9 @@ import { fetchEditPoint } from './editPoint';
import { fetchSelectItem } from './selectItem';
import { evaluateMathExpression } from './evaluateMathExpression';
import { incrementDecrement } from './incrementDecrement';
import { LANGUAGE_MODES, getMappingForIncludedLanguages, updateEmmetExtensionsPath, getPathBaseName, toLSTextDocument, getSyntaxes, getEmmetMode } from './util';
import { LANGUAGE_MODES, getMappingForIncludedLanguages, updateEmmetExtensionsPath, getPathBaseName, getSyntaxes, getEmmetMode } from './util';
import { reflectCssValue } from './reflectCssValue';
import { addFileToMarkupParseCache, removeFileFromMarkupParseCache } from './parseMarkupDocument';
import { addFileToParseCache, removeFileFromParseCache } from './parseDocument';
export function activateEmmetExtension(context: vscode.ExtensionContext) {
registerCompletionProviders(context);
@ -149,15 +149,17 @@ export function activateEmmetExtension(context: vscode.ExtensionContext) {
context.subscriptions.push(vscode.workspace.onDidOpenTextDocument((e) => {
const emmetMode = getEmmetMode(e.languageId, []) ?? '';
if (getSyntaxes().markup.includes(emmetMode)) {
addFileToMarkupParseCache(toLSTextDocument(e));
const syntaxes = getSyntaxes();
if (syntaxes.markup.includes(emmetMode) || syntaxes.stylesheet.includes(emmetMode)) {
addFileToParseCache(e);
}
}));
context.subscriptions.push(vscode.workspace.onDidCloseTextDocument((e) => {
const emmetMode = getEmmetMode(e.languageId, []) ?? '';
if (getSyntaxes().markup.includes(emmetMode)) {
removeFileFromMarkupParseCache(toLSTextDocument(e));
const syntaxes = getSyntaxes();
if (syntaxes.markup.includes(emmetMode) || syntaxes.stylesheet.includes(emmetMode)) {
removeFileFromParseCache(e);
}
}));
}

View file

@ -7,7 +7,6 @@
import * as vscode from 'vscode';
import evaluate, { extract } from '@emmetio/math-expression';
import { DocumentStreamReader } from './bufferStream';
export function evaluateMathExpression(): Thenable<boolean> {
if (!vscode.window.activeTextEditor) {
@ -15,13 +14,12 @@ export function evaluateMathExpression(): Thenable<boolean> {
return Promise.resolve(false);
}
const editor = vscode.window.activeTextEditor;
const stream = new DocumentStreamReader(editor.document);
return editor.edit(editBuilder => {
editor.selections.forEach(selection => {
// startpos always comes before endpos
const startpos = selection.isReversed ? selection.active : selection.anchor;
const endpos = selection.isReversed ? selection.anchor : selection.active;
const selectionText = stream.substring(startpos, endpos);
const selectionText = editor.document.getText(new vscode.Range(startpos, endpos));
try {
if (selectionText) {
@ -30,7 +28,7 @@ export function evaluateMathExpression(): Thenable<boolean> {
editBuilder.replace(new vscode.Range(startpos, endpos), result);
} else {
// no selection made, extract expression from line
const lineToSelectionEnd = stream.substring(new vscode.Position(selection.end.line, 0), endpos);
const lineToSelectionEnd = editor.document.getText(new vscode.Range(new vscode.Position(selection.end.line, 0), endpos));
const extractedIndices = extract(lineToSelectionEnd);
if (!extractedIndices) {
throw new Error('Invalid extracted indices');

View file

@ -4,7 +4,6 @@
*--------------------------------------------------------------------------------------------*/
// Based on @sergeche's work on the emmet plugin for atom
// TODO: Move to https://github.com/emmetio/image-size
import * as path from 'path';
import * as http from 'http';

View file

@ -4,8 +4,9 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { toLSTextDocument, validate, getHtmlNodeLS, offsetRangeToSelection } from './util';
import { TextDocument as LSTextDocument } from 'vscode-html-languageservice';
import { validate, getHtmlFlatNode, offsetRangeToSelection } from './util';
import { getRootNode } from './parseDocument';
import { HtmlNode as HtmlFlatNode } from 'EmmetFlatNode';
export function matchTag() {
if (!validate(false) || !vscode.window.activeTextEditor) {
@ -13,11 +14,15 @@ export function matchTag() {
}
const editor = vscode.window.activeTextEditor;
const document = toLSTextDocument(editor.document);
const document = editor.document;
const rootNode = <HtmlFlatNode>getRootNode(document, true);
if (!rootNode) {
return;
}
let updatedSelections: vscode.Selection[] = [];
editor.selections.forEach(selection => {
const updatedSelection = getUpdatedSelections(document, selection.start);
const updatedSelection = getUpdatedSelections(document, rootNode, selection.start);
if (updatedSelection) {
updatedSelections.push(updatedSelection);
}
@ -28,22 +33,21 @@ export function matchTag() {
}
}
function getUpdatedSelections(document: LSTextDocument, position: vscode.Position): vscode.Selection | undefined {
const currentNode = getHtmlNodeLS(document, position, true);
function getUpdatedSelections(document: vscode.TextDocument, rootNode: HtmlFlatNode, position: vscode.Position): vscode.Selection | undefined {
const offset = document.offsetAt(position);
const currentNode = getHtmlFlatNode(document.getText(), rootNode, offset, true);
if (!currentNode) {
return;
}
const offset = document.offsetAt(position);
// If no closing tag or cursor is between open and close tag, then no-op
if (currentNode.endTagStart === undefined
|| currentNode.startTagEnd === undefined
|| (offset > currentNode.startTagEnd && offset < currentNode.endTagStart)) {
// If no opening/closing tag or cursor is between open and close tag, then no-op
if (!currentNode.open
|| !currentNode.close
|| (offset > currentNode.open.end && offset < currentNode.close.start)) {
return;
}
// Place cursor inside the close tag if cursor is inside the open tag, else place it inside the open tag
const finalOffset = (offset <= currentNode.startTagEnd) ? currentNode.endTagStart + 2 : currentNode.start + 1;
const finalOffset = (offset <= currentNode.open.end) ? currentNode.close.start + 2 : currentNode.start + 1;
return offsetRangeToSelection(document, finalOffset, finalOffset);
}

View file

@ -4,8 +4,9 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { Node } from 'EmmetNode';
import { getNode, parseDocument, validate } from './util';
import { Node } from 'EmmetFlatNode';
import { getFlatNode, offsetRangeToVsRange, validate } from './util';
import { getRootNode } from './parseDocument';
export function mergeLines() {
if (!validate(false) || !vscode.window.activeTextEditor) {
@ -14,14 +15,14 @@ export function mergeLines() {
const editor = vscode.window.activeTextEditor;
let rootNode = parseDocument(editor.document);
const rootNode = getRootNode(editor.document, true);
if (!rootNode) {
return;
}
return editor.edit(editBuilder => {
editor.selections.reverse().forEach(selection => {
let textEdit = getRangesToReplace(editor.document, selection, rootNode!);
const textEdit = getRangesToReplace(editor.document, selection, rootNode);
if (textEdit) {
editBuilder.replace(textEdit.range, textEdit.newText);
}
@ -30,25 +31,36 @@ export function mergeLines() {
}
function getRangesToReplace(document: vscode.TextDocument, selection: vscode.Selection, rootNode: Node): vscode.TextEdit | undefined {
let startNodeToUpdate: Node | null;
let endNodeToUpdate: Node | null;
let startNodeToUpdate: Node | undefined;
let endNodeToUpdate: Node | undefined;
const selectionStart = document.offsetAt(selection.start);
const selectionEnd = document.offsetAt(selection.end);
if (selection.isEmpty) {
startNodeToUpdate = endNodeToUpdate = getNode(rootNode, selection.start, true);
startNodeToUpdate = endNodeToUpdate = getFlatNode(rootNode, selectionStart, true);
} else {
startNodeToUpdate = getNode(rootNode, selection.start, true);
endNodeToUpdate = getNode(rootNode, selection.end, true);
startNodeToUpdate = getFlatNode(rootNode, selectionStart, true);
endNodeToUpdate = getFlatNode(rootNode, selectionEnd, true);
}
if (!startNodeToUpdate || !endNodeToUpdate || startNodeToUpdate.start.line === endNodeToUpdate.end.line) {
if (!startNodeToUpdate || !endNodeToUpdate) {
return;
}
let rangeToReplace = new vscode.Range(startNodeToUpdate.start, endNodeToUpdate.end);
let textToReplaceWith = document.lineAt(startNodeToUpdate.start.line).text.substr(startNodeToUpdate.start.character);
for (let i = startNodeToUpdate.start.line + 1; i <= endNodeToUpdate.end.line; i++) {
const startPos = document.positionAt(startNodeToUpdate.start);
const startLine = startPos.line;
const startChar = startPos.character;
const endPos = document.positionAt(endNodeToUpdate.end);
const endLine = endPos.line;
if (startLine === endLine) {
return;
}
const rangeToReplace = offsetRangeToVsRange(document, startNodeToUpdate.start, endNodeToUpdate.end);
let textToReplaceWith = document.lineAt(startLine).text.substr(startChar);
for (let i = startLine + 1; i <= endLine; i++) {
textToReplaceWith += document.lineAt(i).text.trim();
}
return new vscode.TextEdit(rangeToReplace, textToReplaceWith);
}
}

View file

@ -0,0 +1,46 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { TextDocument } from 'vscode';
import { Node as FlatNode } from 'EmmetFlatNode';
import parse from '@emmetio/html-matcher';
import parseStylesheet from '@emmetio/css-parser';
import { isStyleSheet } from './util';
type Pair<K, V> = {
key: K;
value: V;
};
// Map(filename, Pair(fileVersion, rootNodeOfParsedContent))
const _parseCache = new Map<string, Pair<number, FlatNode> | undefined>();
export function getRootNode(document: TextDocument, useCache: boolean): FlatNode {
const key = document.uri.toString();
const result = _parseCache.get(key);
const documentVersion = document.version;
if (useCache && result) {
if (documentVersion === result.key) {
return result.value;
}
}
const parseContent = isStyleSheet(document.languageId) ? parseStylesheet : parse;
const rootNode = parseContent(document.getText());
if (useCache) {
_parseCache.set(key, { key: documentVersion, value: rootNode });
}
return rootNode;
}
export function addFileToParseCache(document: TextDocument) {
const filename = document.uri.toString();
_parseCache.set(filename, undefined);
}
export function removeFileFromParseCache(document: TextDocument) {
const filename = document.uri.toString();
_parseCache.delete(filename);
}

View file

@ -1,43 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { HTMLDocument, TextDocument as LSTextDocument } from 'vscode-html-languageservice';
import { getLanguageService } from './util';
type Pair<K, V> = {
key: K;
value: V;
};
// Map(filename, Pair(fileVersion, parsedContent))
const _parseCache = new Map<string, Pair<number, HTMLDocument> | undefined>();
export function parseMarkupDocument(document: LSTextDocument, useCache: boolean = true): HTMLDocument {
const languageService = getLanguageService();
const key = document.uri;
const result = _parseCache.get(key);
const documentVersion = document.version;
if (useCache && result) {
if (documentVersion === result.key) {
return result.value;
}
}
const parsedDocument = languageService.parseHTMLDocument(document);
if (useCache) {
_parseCache.set(key, { key: documentVersion, value: parsedDocument });
}
return parsedDocument;
}
export function addFileToMarkupParseCache(document: LSTextDocument) {
const filename = document.uri;
_parseCache.set(filename, undefined);
}
export function removeFileFromMarkupParseCache(document: LSTextDocument) {
const filename = document.uri;
_parseCache.delete(filename);
}

View file

@ -3,20 +3,20 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Range, window, TextEditor } from 'vscode';
import { getCssPropertyFromRule, getCssPropertyFromDocument } from './util';
import { Property, Rule } from 'EmmetNode';
import { window, TextEditor } from 'vscode';
import { getCssPropertyFromRule, getCssPropertyFromDocument, offsetRangeToVsRange } from './util';
import { Property, Rule } from 'EmmetFlatNode';
const vendorPrefixes = ['-webkit-', '-moz-', '-ms-', '-o-', ''];
export function reflectCssValue(): Thenable<boolean> | undefined {
let editor = window.activeTextEditor;
const editor = window.activeTextEditor;
if (!editor) {
window.showInformationMessage('No editor is active.');
return;
}
let node = getCssPropertyFromDocument(editor, editor.selection.active);
const node = getCssPropertyFromDocument(editor, editor.selection.active);
if (!node) {
return;
}
@ -45,10 +45,11 @@ function updateCSSNode(editor: TextEditor, property: Property): Thenable<boolean
if (prefix === currentPrefix) {
return;
}
let vendorProperty = getCssPropertyFromRule(rule, prefix + propertyName);
const vendorProperty = getCssPropertyFromRule(rule, prefix + propertyName);
if (vendorProperty) {
builder.replace(new Range(vendorProperty.valueToken.start, vendorProperty.valueToken.end), propertyValue);
const rangeToReplace = offsetRangeToVsRange(editor.document, vendorProperty.valueToken.start, vendorProperty.valueToken.end);
builder.replace(rangeToReplace, propertyValue);
}
});
});
}
}

View file

@ -4,16 +4,24 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { validate, getHtmlNodeLS, toLSTextDocument, offsetRangeToVsRange } from './util';
import { getRootNode } from './parseDocument';
import { validate, getHtmlFlatNode, offsetRangeToVsRange } from './util';
import { HtmlNode as HtmlFlatNode } from 'EmmetFlatNode';
export function removeTag() {
if (!validate(false) || !vscode.window.activeTextEditor) {
return;
}
const editor = vscode.window.activeTextEditor;
const document = editor.document;
const rootNode = <HtmlFlatNode>getRootNode(document, true);
if (!rootNode) {
return;
}
let finalRangesToRemove = editor.selections.reverse()
.reduce<vscode.Range[]>((prev, selection) =>
prev.concat(getRangesToRemove(editor.document, selection)), []);
prev.concat(getRangesToRemove(editor.document, rootNode, selection)), []);
return editor.edit(editBuilder => {
finalRangesToRemove.forEach(range => {
@ -27,26 +35,32 @@ export function removeTag() {
* It finds the node to remove based on the selection's start position
* and then removes that node, reindenting the content in between.
*/
function getRangesToRemove(document: vscode.TextDocument, selection: vscode.Selection): vscode.Range[] {
const lsDocument = toLSTextDocument(document);
const nodeToUpdate = getHtmlNodeLS(lsDocument, selection.start, true);
function getRangesToRemove(document: vscode.TextDocument, rootNode: HtmlFlatNode, selection: vscode.Selection): vscode.Range[] {
const offset = document.offsetAt(selection.start);
const nodeToUpdate = getHtmlFlatNode(document.getText(), rootNode, offset, true);
if (!nodeToUpdate) {
return [];
}
const openTagRange = offsetRangeToVsRange(lsDocument, nodeToUpdate.start, nodeToUpdate.startTagEnd ?? nodeToUpdate.end);
let openTagRange: vscode.Range | undefined;
if (nodeToUpdate.open) {
openTagRange = offsetRangeToVsRange(document, nodeToUpdate.open.start, nodeToUpdate.open.end);
}
let closeTagRange: vscode.Range | undefined;
if (nodeToUpdate.endTagStart !== undefined) {
closeTagRange = offsetRangeToVsRange(lsDocument, nodeToUpdate.endTagStart, nodeToUpdate.end);
if (nodeToUpdate.close) {
closeTagRange = offsetRangeToVsRange(document, nodeToUpdate.close.start, nodeToUpdate.close.end);
}
let rangesToRemove = [openTagRange];
if (closeTagRange) {
const indentAmountToRemove = calculateIndentAmountToRemove(document, openTagRange, closeTagRange);
for (let i = openTagRange.start.line + 1; i < closeTagRange.start.line; i++) {
rangesToRemove.push(new vscode.Range(i, 0, i, indentAmountToRemove));
let rangesToRemove = [];
if (openTagRange) {
rangesToRemove.push(openTagRange);
if (closeTagRange) {
const indentAmountToRemove = calculateIndentAmountToRemove(document, openTagRange, closeTagRange);
for (let i = openTagRange.start.line + 1; i < closeTagRange.start.line; i++) {
rangesToRemove.push(new vscode.Range(i, 0, i, indentAmountToRemove));
}
rangesToRemove.push(closeTagRange);
}
rangesToRemove.push(closeTagRange);
}
return rangesToRemove;
}

View file

@ -4,17 +4,19 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { validate, parseDocument, isStyleSheet } from './util';
import { validate, isStyleSheet } from './util';
import { nextItemHTML, prevItemHTML } from './selectItemHTML';
import { nextItemStylesheet, prevItemStylesheet } from './selectItemStylesheet';
import { HtmlNode, CssNode } from 'EmmetNode';
import { HtmlNode, CssNode } from 'EmmetFlatNode';
import { getRootNode } from './parseDocument';
export function fetchSelectItem(direction: string): void {
if (!validate() || !vscode.window.activeTextEditor) {
return;
}
const editor = vscode.window.activeTextEditor;
let rootNode = parseDocument(editor.document);
const document = editor.document;
const rootNode = getRootNode(document, true);
if (!rootNode) {
return;
}
@ -26,12 +28,16 @@ export function fetchSelectItem(direction: string): void {
let updatedSelection;
if (isStyleSheet(editor.document.languageId)) {
updatedSelection = direction === 'next' ? nextItemStylesheet(selectionStart, selectionEnd, <CssNode>rootNode!) : prevItemStylesheet(selectionStart, selectionEnd, <CssNode>rootNode!);
updatedSelection = direction === 'next' ?
nextItemStylesheet(document, selectionStart, selectionEnd, <CssNode>rootNode) :
prevItemStylesheet(document, selectionStart, selectionEnd, <CssNode>rootNode);
} else {
updatedSelection = direction === 'next' ? nextItemHTML(selectionStart, selectionEnd, editor, <HtmlNode>rootNode!) : prevItemHTML(selectionStart, selectionEnd, editor, <HtmlNode>rootNode!);
updatedSelection = direction === 'next' ?
nextItemHTML(document, selectionStart, selectionEnd, <HtmlNode>rootNode) :
prevItemHTML(document, selectionStart, selectionEnd, <HtmlNode>rootNode);
}
newSelections.push(updatedSelection ? updatedSelection : selection);
});
editor.selections = newSelections;
editor.revealRange(editor.selections[editor.selections.length - 1]);
}
}

View file

@ -4,11 +4,12 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { getDeepestNode, findNextWord, findPrevWord, getHtmlNode, isNumber } from './util';
import { HtmlNode } from 'EmmetNode';
import { getDeepestFlatNode, findNextWord, findPrevWord, getHtmlFlatNode, offsetRangeToSelection } from './util';
import { HtmlNode } from 'EmmetFlatNode';
export function nextItemHTML(selectionStart: vscode.Position, selectionEnd: vscode.Position, editor: vscode.TextEditor, rootNode: HtmlNode): vscode.Selection | undefined {
let currentNode = getHtmlNode(editor.document, rootNode, selectionEnd, false);
export function nextItemHTML(document: vscode.TextDocument, selectionStart: vscode.Position, selectionEnd: vscode.Position, rootNode: HtmlNode): vscode.Selection | undefined {
const selectionEndOffset = document.offsetAt(selectionEnd);
let currentNode = getHtmlFlatNode(document.getText(), rootNode, selectionEndOffset, false);
let nextNode: HtmlNode | undefined = undefined;
if (!currentNode) {
@ -17,13 +18,16 @@ export function nextItemHTML(selectionStart: vscode.Position, selectionEnd: vsco
if (currentNode.type !== 'comment') {
// If cursor is in the tag name, select tag
if (selectionEnd.isBefore(currentNode.open.start.translate(0, currentNode.name.length))) {
return getSelectionFromNode(currentNode);
if (currentNode.open &&
selectionEndOffset < currentNode.open.start + currentNode.name.length) {
return getSelectionFromNode(document, currentNode);
}
// If cursor is in the open tag, look for attributes
if (selectionEnd.isBefore(currentNode.open.end)) {
let attrSelection = getNextAttribute(selectionStart, selectionEnd, currentNode);
if (currentNode.open &&
selectionEndOffset < currentNode.open.end) {
const selectionStartOffset = document.offsetAt(selectionStart);
const attrSelection = getNextAttribute(document, selectionStartOffset, selectionEndOffset, currentNode);
if (attrSelection) {
return attrSelection;
}
@ -31,12 +35,11 @@ export function nextItemHTML(selectionStart: vscode.Position, selectionEnd: vsco
// Get the first child of current node which is right after the cursor and is not a comment
nextNode = currentNode.firstChild;
while (nextNode && (selectionEnd.isAfterOrEqual(nextNode.end) || nextNode.type === 'comment')) {
while (nextNode && (selectionEndOffset >= nextNode.end || nextNode.type === 'comment')) {
nextNode = nextNode.nextSibling;
}
}
// Get next sibling of current node which is not a comment. If none is found try the same on the parent
while (!nextNode && currentNode) {
if (currentNode.nextSibling) {
@ -50,33 +53,36 @@ export function nextItemHTML(selectionStart: vscode.Position, selectionEnd: vsco
}
}
return nextNode && getSelectionFromNode(nextNode);
return nextNode && getSelectionFromNode(document, nextNode);
}
export function prevItemHTML(selectionStart: vscode.Position, selectionEnd: vscode.Position, editor: vscode.TextEditor, rootNode: HtmlNode): vscode.Selection | undefined {
let currentNode = getHtmlNode(editor.document, rootNode, selectionStart, false);
export function prevItemHTML(document: vscode.TextDocument, selectionStart: vscode.Position, selectionEnd: vscode.Position, rootNode: HtmlNode): vscode.Selection | undefined {
const selectionStartOffset = document.offsetAt(selectionStart);
let currentNode = getHtmlFlatNode(document.getText(), rootNode, selectionStartOffset, false);
let prevNode: HtmlNode | undefined = undefined;
if (!currentNode) {
return;
}
if (currentNode.type !== 'comment' && selectionStart.translate(0, -1).isAfter(currentNode.open.start)) {
if (selectionStart.isBefore(currentNode.open.end) || !currentNode.firstChild || selectionEnd.isBeforeOrEqual(currentNode.firstChild.start)) {
const selectionEndOffset = document.offsetAt(selectionEnd);
if (currentNode.open &&
currentNode.type !== 'comment' &&
selectionStartOffset - 1 > currentNode.open.start) {
if (selectionStartOffset < currentNode.open.end || !currentNode.firstChild || selectionEndOffset <= currentNode.firstChild.start) {
prevNode = currentNode;
} else {
// Select the child that appears just before the cursor and is not a comment
prevNode = currentNode.firstChild;
let oldOption: HtmlNode | undefined = undefined;
while (prevNode.nextSibling && selectionStart.isAfterOrEqual(prevNode.nextSibling.end)) {
while (prevNode.nextSibling && selectionStartOffset >= prevNode.nextSibling.end) {
if (prevNode && prevNode.type !== 'comment') {
oldOption = prevNode;
}
prevNode = prevNode.nextSibling;
}
prevNode = <HtmlNode>getDeepestNode((prevNode && prevNode.type !== 'comment') ? prevNode : oldOption);
prevNode = <HtmlNode>getDeepestFlatNode((prevNode && prevNode.type !== 'comment') ? prevNode : oldOption);
}
}
@ -84,7 +90,7 @@ export function prevItemHTML(selectionStart: vscode.Position, selectionEnd: vsco
while (!prevNode && currentNode) {
if (currentNode.previousSibling) {
if (currentNode.previousSibling.type !== 'comment') {
prevNode = <HtmlNode>getDeepestNode(currentNode.previousSibling);
prevNode = <HtmlNode>getDeepestFlatNode(currentNode.previousSibling);
} else {
currentNode = currentNode.previousSibling;
}
@ -98,66 +104,66 @@ export function prevItemHTML(selectionStart: vscode.Position, selectionEnd: vsco
return undefined;
}
let attrSelection = getPrevAttribute(selectionStart, selectionEnd, prevNode);
return attrSelection ? attrSelection : getSelectionFromNode(prevNode);
const attrSelection = getPrevAttribute(document, selectionStartOffset, selectionEndOffset, prevNode);
return attrSelection ? attrSelection : getSelectionFromNode(document, prevNode);
}
function getSelectionFromNode(node: HtmlNode): vscode.Selection | undefined {
function getSelectionFromNode(document: vscode.TextDocument, node: HtmlNode): vscode.Selection | undefined {
if (node && node.open) {
let selectionStart = (<vscode.Position>node.open.start).translate(0, 1);
let selectionEnd = selectionStart.translate(0, node.name.length);
return new vscode.Selection(selectionStart, selectionEnd);
const selectionStart = node.open.start + 1;
const selectionEnd = selectionStart + node.name.length;
return offsetRangeToSelection(document, selectionStart, selectionEnd);
}
return undefined;
}
function getNextAttribute(selectionStart: vscode.Position, selectionEnd: vscode.Position, node: HtmlNode): vscode.Selection | undefined {
function getNextAttribute(document: vscode.TextDocument, selectionStart: number, selectionEnd: number, node: HtmlNode): vscode.Selection | undefined {
if (!node.attributes || node.attributes.length === 0 || node.type === 'comment') {
return;
}
for (const attr of node.attributes) {
if (selectionEnd.isBefore(attr.start)) {
if (selectionEnd < attr.start) {
// select full attr
return new vscode.Selection(attr.start, attr.end);
return offsetRangeToSelection(document, attr.start, attr.end);
}
if (!attr.value || (<vscode.Position>attr.value.start).isEqual(attr.value.end)) {
if (!attr.value || attr.value.start === attr.value.end) {
// No attr value to select
continue;
}
if ((selectionStart.isEqual(attr.start) && selectionEnd.isEqual(attr.end)) || selectionEnd.isBefore(attr.value.start)) {
if ((selectionStart === attr.start && selectionEnd === attr.end) ||
selectionEnd < attr.value.start) {
// cursor is in attr name, so select full attr value
return new vscode.Selection(attr.value.start, attr.value.end);
return offsetRangeToSelection(document, attr.value.start, attr.value.end);
}
// Fetch the next word in the attr value
if (attr.value.toString().indexOf(' ') === -1) {
// attr value does not have space, so no next word to find
continue;
}
let pos: number | undefined = undefined;
if (selectionStart.isEqual(attr.value.start) && selectionEnd.isEqual(attr.value.end)) {
if (selectionStart === attr.value.start && selectionEnd === attr.value.end) {
pos = -1;
}
if (pos === undefined && selectionEnd.isBefore(attr.end)) {
pos = selectionEnd.character - attr.value.start.character - 1;
if (pos === undefined && selectionEnd < attr.end) {
const selectionEndCharacter = document.positionAt(selectionEnd).character;
const attrValueStartCharacter = document.positionAt(attr.value.start).character;
pos = selectionEndCharacter - attrValueStartCharacter - 1;
}
if (pos !== undefined) {
let [newSelectionStartOffset, newSelectionEndOffset] = findNextWord(attr.value.toString(), pos);
if (!isNumber(newSelectionStartOffset) || !isNumber(newSelectionEndOffset)) {
const [newSelectionStartOffset, newSelectionEndOffset] = findNextWord(attr.value.toString(), pos);
if (newSelectionStartOffset === undefined || newSelectionEndOffset === undefined) {
return;
}
if (newSelectionStartOffset >= 0 && newSelectionEndOffset >= 0) {
const newSelectionStart = (<vscode.Position>attr.value.start).translate(0, newSelectionStartOffset);
const newSelectionEnd = (<vscode.Position>attr.value.start).translate(0, newSelectionEndOffset);
return new vscode.Selection(newSelectionStart, newSelectionEnd);
const newSelectionStart = attr.value.start + newSelectionStartOffset;
const newSelectionEnd = attr.value.start + newSelectionEndOffset;
return offsetRangeToSelection(document, newSelectionStart, newSelectionEnd);
}
}
@ -166,44 +172,44 @@ function getNextAttribute(selectionStart: vscode.Position, selectionEnd: vscode.
return;
}
function getPrevAttribute(selectionStart: vscode.Position, selectionEnd: vscode.Position, node: HtmlNode): vscode.Selection | undefined {
function getPrevAttribute(document: vscode.TextDocument, selectionStart: number, selectionEnd: number, node: HtmlNode): vscode.Selection | undefined {
if (!node.attributes || node.attributes.length === 0 || node.type === 'comment') {
return;
}
for (let i = node.attributes.length - 1; i >= 0; i--) {
let attr = node.attributes[i];
if (selectionStart.isBeforeOrEqual(attr.start)) {
const attr = node.attributes[i];
if (selectionStart <= attr.start) {
continue;
}
if (!attr.value || (<vscode.Position>attr.value.start).isEqual(attr.value.end) || selectionStart.isBefore(attr.value.start)) {
if (!attr.value || attr.value.start === attr.value.end || selectionStart < attr.value.start) {
// select full attr
return new vscode.Selection(attr.start, attr.end);
return offsetRangeToSelection(document, attr.start, attr.end);
}
if (selectionStart.isEqual(attr.value.start)) {
if (selectionEnd.isAfterOrEqual(attr.value.end)) {
if (selectionStart === attr.value.start) {
if (selectionEnd >= attr.value.end) {
// select full attr
return new vscode.Selection(attr.start, attr.end);
return offsetRangeToSelection(document, attr.start, attr.end);
}
// select attr value
return new vscode.Selection(attr.value.start, attr.value.end);
return offsetRangeToSelection(document, attr.value.start, attr.value.end);
}
// Fetch the prev word in the attr value
let pos = selectionStart.isAfter(attr.value.end) ? attr.value.toString().length : selectionStart.character - attr.value.start.character;
let [newSelectionStartOffset, newSelectionEndOffset] = findPrevWord(attr.value.toString(), pos);
if (!isNumber(newSelectionStartOffset) || !isNumber(newSelectionEndOffset)) {
const selectionStartCharacter = document.positionAt(selectionStart).character;
const attrValueStartCharacter = document.positionAt(attr.value.start).character;
const pos = selectionStart > attr.value.end ? attr.value.toString().length :
selectionStartCharacter - attrValueStartCharacter;
const [newSelectionStartOffset, newSelectionEndOffset] = findPrevWord(attr.value.toString(), pos);
if (newSelectionStartOffset === undefined || newSelectionEndOffset === undefined) {
return;
}
if (newSelectionStartOffset >= 0 && newSelectionEndOffset >= 0) {
const newSelectionStart = (<vscode.Position>attr.value.start).translate(0, newSelectionStartOffset);
const newSelectionEnd = (<vscode.Position>attr.value.start).translate(0, newSelectionEndOffset);
return new vscode.Selection(newSelectionStart, newSelectionEnd);
const newSelectionStart = attr.value.start + newSelectionStartOffset;
const newSelectionEnd = attr.value.start + newSelectionEndOffset;
return offsetRangeToSelection(document, newSelectionStart, newSelectionEnd);
}
}

View file

@ -4,11 +4,13 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { getDeepestNode, findNextWord, findPrevWord, getNode } from './util';
import { Node, CssNode, Rule, Property } from 'EmmetNode';
import { getDeepestFlatNode, findNextWord, findPrevWord, getFlatNode, offsetRangeToSelection } from './util';
import { Node, CssNode, Rule, Property } from 'EmmetFlatNode';
export function nextItemStylesheet(startOffset: vscode.Position, endOffset: vscode.Position, rootNode: Node): vscode.Selection | undefined {
let currentNode = <CssNode>getNode(rootNode, endOffset, true);
export function nextItemStylesheet(document: vscode.TextDocument, startPosition: vscode.Position, endPosition: vscode.Position, rootNode: Node): vscode.Selection | undefined {
const startOffset = document.offsetAt(startPosition);
const endOffset = document.offsetAt(endPosition);
let currentNode: CssNode | undefined = <CssNode>getFlatNode(rootNode, endOffset, true);
if (!currentNode) {
currentNode = <CssNode>rootNode;
}
@ -16,27 +18,31 @@ export function nextItemStylesheet(startOffset: vscode.Position, endOffset: vsco
return;
}
// Full property is selected, so select full property value next
if (currentNode.type === 'property' && startOffset.isEqual(currentNode.start) && endOffset.isEqual(currentNode.end)) {
return getSelectionFromProperty(currentNode, startOffset, endOffset, true, 'next');
if (currentNode.type === 'property' &&
startOffset === currentNode.start &&
endOffset === currentNode.end) {
return getSelectionFromProperty(document, currentNode, startOffset, endOffset, true, 'next');
}
// Part or whole of propertyValue is selected, so select the next word in the propertyValue
if (currentNode.type === 'property' && startOffset.isAfterOrEqual((<Property>currentNode).valueToken.start) && endOffset.isBeforeOrEqual((<Property>currentNode).valueToken.end)) {
let singlePropertyValue = getSelectionFromProperty(currentNode, startOffset, endOffset, false, 'next');
if (currentNode.type === 'property' &&
startOffset >= (<Property>currentNode).valueToken.start &&
endOffset <= (<Property>currentNode).valueToken.end) {
let singlePropertyValue = getSelectionFromProperty(document, currentNode, startOffset, endOffset, false, 'next');
if (singlePropertyValue) {
return singlePropertyValue;
}
}
// Cursor is in the selector or in a property
if ((currentNode.type === 'rule' && endOffset.isBefore((<Rule>currentNode).selectorToken.end))
|| (currentNode.type === 'property' && endOffset.isBefore((<Property>currentNode).valueToken.end))) {
return getSelectionFromNode(currentNode);
if ((currentNode.type === 'rule' && endOffset < (<Rule>currentNode).selectorToken.end)
|| (currentNode.type === 'property' && endOffset < (<Property>currentNode).valueToken.end)) {
return getSelectionFromNode(document, currentNode);
}
// Get the first child of current node which is right after the cursor
let nextNode = currentNode.firstChild;
while (nextNode && endOffset.isAfterOrEqual(nextNode.end)) {
while (nextNode && endOffset >= nextNode.end) {
nextNode = nextNode.nextSibling;
}
@ -46,12 +52,13 @@ export function nextItemStylesheet(startOffset: vscode.Position, endOffset: vsco
currentNode = currentNode.parent;
}
return getSelectionFromNode(nextNode);
return nextNode ? getSelectionFromNode(document, nextNode) : undefined;
}
export function prevItemStylesheet(startOffset: vscode.Position, endOffset: vscode.Position, rootNode: CssNode): vscode.Selection | undefined {
let currentNode = <CssNode>getNode(rootNode, startOffset, false);
export function prevItemStylesheet(document: vscode.TextDocument, startPosition: vscode.Position, endPosition: vscode.Position, rootNode: CssNode): vscode.Selection | undefined {
const startOffset = document.offsetAt(startPosition);
const endOffset = document.offsetAt(endPosition);
let currentNode = <CssNode>getFlatNode(rootNode, startOffset, false);
if (!currentNode) {
currentNode = rootNode;
}
@ -60,70 +67,80 @@ export function prevItemStylesheet(startOffset: vscode.Position, endOffset: vsco
}
// Full property value is selected, so select the whole property next
if (currentNode.type === 'property' && startOffset.isEqual((<Property>currentNode).valueToken.start) && endOffset.isEqual((<Property>currentNode).valueToken.end)) {
return getSelectionFromNode(currentNode);
if (currentNode.type === 'property' &&
startOffset === (<Property>currentNode).valueToken.start &&
endOffset === (<Property>currentNode).valueToken.end) {
return getSelectionFromNode(document, currentNode);
}
// Part of propertyValue is selected, so select the prev word in the propertyValue
if (currentNode.type === 'property' && startOffset.isAfterOrEqual((<Property>currentNode).valueToken.start) && endOffset.isBeforeOrEqual((<Property>currentNode).valueToken.end)) {
let singlePropertyValue = getSelectionFromProperty(currentNode, startOffset, endOffset, false, 'prev');
if (currentNode.type === 'property' &&
startOffset >= (<Property>currentNode).valueToken.start &&
endOffset <= (<Property>currentNode).valueToken.end) {
let singlePropertyValue = getSelectionFromProperty(document, currentNode, startOffset, endOffset, false, 'prev');
if (singlePropertyValue) {
return singlePropertyValue;
}
}
if (currentNode.type === 'property' || !currentNode.firstChild || (currentNode.type === 'rule' && startOffset.isBeforeOrEqual(currentNode.firstChild.start))) {
return getSelectionFromNode(currentNode);
if (currentNode.type === 'property' || !currentNode.firstChild ||
(currentNode.type === 'rule' && startOffset <= currentNode.firstChild.start)) {
return getSelectionFromNode(document, currentNode);
}
// Select the child that appears just before the cursor
let prevNode = currentNode.firstChild;
while (prevNode.nextSibling && startOffset.isAfterOrEqual(prevNode.nextSibling.end)) {
let prevNode: CssNode | undefined = currentNode.firstChild;
while (prevNode.nextSibling && startOffset >= prevNode.nextSibling.end) {
prevNode = prevNode.nextSibling;
}
prevNode = <CssNode>getDeepestNode(prevNode);
return getSelectionFromProperty(prevNode, startOffset, endOffset, false, 'prev');
prevNode = <CssNode | undefined>getDeepestFlatNode(prevNode);
return getSelectionFromProperty(document, prevNode, startOffset, endOffset, false, 'prev');
}
function getSelectionFromNode(node: Node): vscode.Selection | undefined {
function getSelectionFromNode(document: vscode.TextDocument, node: Node | undefined): vscode.Selection | undefined {
if (!node) {
return;
}
let nodeToSelect = node.type === 'rule' ? (<Rule>node).selectorToken : node;
return new vscode.Selection(nodeToSelect.start, nodeToSelect.end);
const nodeToSelect = node.type === 'rule' ? (<Rule>node).selectorToken : node;
return offsetRangeToSelection(document, nodeToSelect.start, nodeToSelect.end);
}
function getSelectionFromProperty(node: Node, selectionStart: vscode.Position, selectionEnd: vscode.Position, selectFullValue: boolean, direction: string): vscode.Selection | undefined {
function getSelectionFromProperty(document: vscode.TextDocument, node: Node | undefined, selectionStart: number, selectionEnd: number, selectFullValue: boolean, direction: string): vscode.Selection | undefined {
if (!node || node.type !== 'property') {
return;
}
const propertyNode = <Property>node;
let propertyValue = propertyNode.valueToken.stream.substring(propertyNode.valueToken.start, propertyNode.valueToken.end);
selectFullValue = selectFullValue || (direction === 'prev' && selectionStart.isEqual(propertyNode.valueToken.start) && selectionEnd.isBefore(propertyNode.valueToken.end));
selectFullValue = selectFullValue ||
(direction === 'prev' && selectionStart === propertyNode.valueToken.start && selectionEnd < propertyNode.valueToken.end);
if (selectFullValue) {
return new vscode.Selection(propertyNode.valueToken.start, propertyNode.valueToken.end);
return offsetRangeToSelection(document, propertyNode.valueToken.start, propertyNode.valueToken.end);
}
let pos: number = -1;
if (direction === 'prev') {
if (selectionStart.isEqual(propertyNode.valueToken.start)) {
if (selectionStart === propertyNode.valueToken.start) {
return;
}
pos = selectionStart.isAfter(propertyNode.valueToken.end) ? propertyValue.length : selectionStart.character - propertyNode.valueToken.start.character;
}
if (direction === 'next') {
if (selectionEnd.isEqual(propertyNode.valueToken.end) && (selectionStart.isAfter(propertyNode.valueToken.start) || propertyValue.indexOf(' ') === -1)) {
const selectionStartChar = document.positionAt(selectionStart).character;
const tokenStartChar = document.positionAt(propertyNode.valueToken.start).character;
pos = selectionStart > propertyNode.valueToken.end ? propertyValue.length :
selectionStartChar - tokenStartChar;
} else if (direction === 'next') {
if (selectionEnd === propertyNode.valueToken.end &&
(selectionStart > propertyNode.valueToken.start || !propertyValue.includes(' '))) {
return;
}
pos = selectionEnd.isEqual(propertyNode.valueToken.end) ? -1 : selectionEnd.character - propertyNode.valueToken.start.character - 1;
const selectionEndChar = document.positionAt(selectionEnd).character;
const tokenStartChar = document.positionAt(propertyNode.valueToken.start).character;
pos = selectionEnd === propertyNode.valueToken.end ? -1 :
selectionEndChar - tokenStartChar - 1;
}
@ -132,8 +149,9 @@ function getSelectionFromProperty(node: Node, selectionStart: vscode.Position, s
return;
}
const newSelectionStart = (<vscode.Position>propertyNode.valueToken.start).translate(0, newSelectionStartOffset);
const newSelectionEnd = (<vscode.Position>propertyNode.valueToken.start).translate(0, newSelectionEndOffset);
const tokenStart = document.positionAt(propertyNode.valueToken.start);
const newSelectionStart = tokenStart.translate(0, newSelectionStartOffset);
const newSelectionEnd = tokenStart.translate(0, newSelectionEndOffset);
return new vscode.Selection(newSelectionStart, newSelectionEnd);
}

View file

@ -4,8 +4,9 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { validate, getEmmetMode, getEmmetConfiguration, toLSTextDocument, getHtmlNodeLS, offsetRangeToVsRange } from './util';
import { Node as LSNode, TextDocument as LSTextDocument } from 'vscode-html-languageservice';
import { validate, getEmmetMode, getEmmetConfiguration, getHtmlFlatNode, offsetRangeToVsRange } from './util';
import { HtmlNode as HtmlFlatNode } from 'EmmetFlatNode';
import { getRootNode } from './parseDocument';
export function splitJoinTag() {
if (!validate(false) || !vscode.window.activeTextEditor) {
@ -13,29 +14,30 @@ export function splitJoinTag() {
}
const editor = vscode.window.activeTextEditor;
const document = toLSTextDocument(editor.document);
const document = editor.document;
const rootNode = <HtmlFlatNode>getRootNode(editor.document, true);
if (!rootNode) {
return;
}
return editor.edit(editBuilder => {
editor.selections.reverse().forEach(selection => {
const nodeToUpdate = getHtmlNodeLS(document, selection.start, true);
const documentText = document.getText();
const offset = document.offsetAt(selection.start);
const nodeToUpdate = getHtmlFlatNode(documentText, rootNode, offset, true);
if (nodeToUpdate) {
const textEdit = getRangesToReplace(document, nodeToUpdate);
if (textEdit) {
editBuilder.replace(textEdit.range, textEdit.newText);
}
editBuilder.replace(textEdit.range, textEdit.newText);
}
});
});
}
function getRangesToReplace(document: LSTextDocument, nodeToUpdate: LSNode): vscode.TextEdit | undefined {
function getRangesToReplace(document: vscode.TextDocument, nodeToUpdate: HtmlFlatNode): vscode.TextEdit {
let rangeToReplace: vscode.Range;
let textToReplaceWith: string;
if (!nodeToUpdate?.tag) {
return;
}
if (nodeToUpdate.endTagStart === undefined || nodeToUpdate.startTagEnd === undefined) {
if (!nodeToUpdate.open || !nodeToUpdate.close) {
// Split Tag
const nodeText = document.getText().substring(nodeToUpdate.start, nodeToUpdate.end);
const m = nodeText.match(/(\s*\/)?>$/);
@ -43,10 +45,10 @@ function getRangesToReplace(document: LSTextDocument, nodeToUpdate: LSNode): vsc
const start = m ? end - m[0].length : end;
rangeToReplace = offsetRangeToVsRange(document, start, end);
textToReplaceWith = `></${nodeToUpdate.tag}>`;
textToReplaceWith = `></${nodeToUpdate.name}>`;
} else {
// Join Tag
const start = nodeToUpdate.startTagEnd - 1;
const start = nodeToUpdate.open.end - 1;
const end = nodeToUpdate.end;
rangeToReplace = offsetRangeToVsRange(document, start, end);
textToReplaceWith = '/>';

View file

@ -7,15 +7,16 @@ import 'mocha';
import * as assert from 'assert';
import { withRandomFileEditor } from './testUtils';
import * as vscode from 'vscode';
import { parsePartialStylesheet, getNode } from '../util';
import { parsePartialStylesheet, getFlatNode } from '../util';
import { isValidLocationForEmmetAbbreviation } from '../abbreviationActions';
suite('Tests for partial parse of Stylesheets', () => {
function isValid(doc: vscode.TextDocument, range: vscode.Range, syntax: string): boolean {
const rootNode = parsePartialStylesheet(doc, range.end);
const currentNode = getNode(rootNode, range.end, true);
return isValidLocationForEmmetAbbreviation(doc, rootNode, currentNode, syntax, range.end, range);
const endOffset = doc.offsetAt(range.end);
const currentNode = getFlatNode(rootNode, endOffset, true);
return isValidLocationForEmmetAbbreviation(doc, rootNode, currentNode, syntax, endOffset, range);
}
test('Ignore block comment inside rule', function (): any {
@ -257,4 +258,4 @@ ment */{
});
});
});

View file

@ -26,7 +26,7 @@ suite('Tests for Toggle Comment action from Emmet (HTML)', () => {
<div><li><span>Bye</span></li></div>
</ul>
<ul>
<!--<li>Previously Commented Node</li>-->
<!-- <li>Previously Commented Node</li> -->
<li>Another Node</li>
</ul>
<span/>
@ -47,24 +47,24 @@ suite('Tests for Toggle Comment action from Emmet (HTML)', () => {
const expectedContents = `
<div class="hello">
<ul>
<li><!--<span>Hello</span>--></li>
<!--<li><span>There</span></li>-->
<!--<div><li><span>Bye</span></li></div>-->
<li><!-- <span>Hello</span> --></li>
<!-- <li><span>There</span></li> -->
<!-- <div><li><span>Bye</span></li></div> -->
</ul>
<!--<ul>
<!-- <ul>
<li>Previously Commented Node</li>
<li>Another Node</li>
</ul>-->
</ul> -->
<span/>
<style>
.boo {
/*margin: 10px;*/
/* margin: 10px; */
padding: 20px;
}
/*.hoo {
/* .hoo {
margin: 10px;
padding: 20px;
}*/
} */
</style>
</div>
`;
@ -89,24 +89,24 @@ suite('Tests for Toggle Comment action from Emmet (HTML)', () => {
const expectedContents = `
<div class="hello">
<ul>
<li><!--<span>Hello</span>--></li>
<!--<li><span>There</span></li>-->
<li><!-- <span>Hello</span> --></li>
<!-- <li><span>There</span></li> -->
<div><li><span>Bye</span></li></div>
</ul>
<!--<ul>
<!-- <ul>
<li>Previously Commented Node</li>
<li>Another Node</li>
</ul>-->
</ul> -->
<span/>
<style>
.boo {
/*margin: 10px;*/
/* margin: 10px; */
padding: 20px;
}
/*.hoo {
/* .hoo {
margin: 10px;
padding: 20px;
}*/
} */
</style>
</div>
`;
@ -130,19 +130,19 @@ suite('Tests for Toggle Comment action from Emmet (HTML)', () => {
const expectedContents = `
<div class="hello">
<ul>
<!--<li><span>Hello</span></li>
<li><span>There</span></li>-->
<!-- <li><span>Hello</span></li>
<li><span>There</span></li> -->
<div><li><span>Bye</span></li></div>
</ul>
<ul>
<!--<li>Previously Commented Node</li>-->
<!-- <li>Previously Commented Node</li> -->
<li>Another Node</li>
</ul>
<span/>
<style>
.boo {
/*margin: 10px;
padding: 20px;*/
/* margin: 10px;
padding: 20px; */
}
.hoo {
margin: 10px;
@ -168,14 +168,14 @@ suite('Tests for Toggle Comment action from Emmet (HTML)', () => {
const expectedContents = `
<div class="hello">
<ul>
<!--<li><span>Hello</span></li>
<li><span>There</span></li>-->
<!-- <li><span>Hello</span></li>
<li><span>There</span></li> -->
<div><li><span>Bye</span></li></div>
</ul>
<!--<ul>
<!-- <ul>
<li>Previously Commented Node</li>
<li>Another Node</li>
</ul>-->
</ul> -->
<span/>
<style>
.boo {
@ -206,16 +206,16 @@ suite('Tests for Toggle Comment action from Emmet (HTML)', () => {
const expectedContents = `
<div class="hello">
<ul>
<li><!--<span>Hello</span>--></li>
<!--<li><span>There</span></li>-->
<li><!-- <span>Hello</span> --></li>
<!-- <li><span>There</span></li> -->
<div><li><span>Bye</span></li></div>
</ul>
<!--<ul>
<!-- <ul>
<li>Previously Commented Node</li>
<li>Another Node</li>
</ul>-->
</ul> -->
<span/>
<!--<style>
<!-- <style>
.boo {
margin: 10px;
padding: 20px;
@ -224,7 +224,7 @@ suite('Tests for Toggle Comment action from Emmet (HTML)', () => {
margin: 10px;
padding: 20px;
}
</style>-->
</style> -->
</div>
`;
return withRandomFileEditor(contents, 'html', (editor, doc) => {
@ -252,16 +252,16 @@ suite('Tests for Toggle Comment action from Emmet (HTML)', () => {
const templateContents = `
<script type="text/template">
<li><span>Hello</span></li>
<li><!--<span>There</span>--></li>
<li><!-- <span>There</span> --></li>
<div><li><span>Bye</span></li></div>
<span/>
</script>
`;
const expectedContents = `
<script type="text/template">
<!--<li><span>Hello</span></li>-->
<!-- <li><span>Hello</span></li> -->
<li><span>There</span></li>
<div><li><!--<span>Bye</span>--></li></div>
<div><li><!-- <span>Bye</span> --></li></div>
<span/>
</script>
`;
@ -298,13 +298,13 @@ suite('Tests for Toggle Comment action from Emmet (CSS)', () => {
test('toggle comment with multiple cursors, but no selection (CSS)', () => {
const expectedContents = `
.one {
/*margin: 10px;*/
/* margin: 10px; */
padding: 10px;
}
/*.two {
/* .two {
height: 42px;
display: none;
}*/
} */
.three {
width: 42px;
}`;
@ -327,13 +327,13 @@ suite('Tests for Toggle Comment action from Emmet (CSS)', () => {
test('toggle comment with multiple cursors and whole node selected (CSS)', () => {
const expectedContents = `
.one {
/*margin: 10px;*/
/*padding: 10px;*/
/* margin: 10px; */
/* padding: 10px; */
}
/*.two {
/* .two {
height: 42px;
display: none;
}*/
} */
.three {
width: 42px;
}`;
@ -359,16 +359,16 @@ suite('Tests for Toggle Comment action from Emmet (CSS)', () => {
test('toggle comment when multiple nodes of same parent are completely under single selection (CSS)', () => {
const expectedContents = `
.one {
/* margin: 10px;
padding: 10px;*/
/* margin: 10px;
padding: 10px; */
}
/*.two {
/* .two {
height: 42px;
display: none;
}
.three {
width: 42px;
}*/`;
} */`;
return withRandomFileEditor(contents, 'css', (editor, doc) => {
editor.selections = [
new Selection(2, 0, 3, 16), // 2 properties completely under a single selection along with whitespace
@ -389,10 +389,10 @@ suite('Tests for Toggle Comment action from Emmet (CSS)', () => {
const expectedContents = `
.one {
margin: 10px;
/*padding: 10px;
/* padding: 10px;
}
.two {
height: 42px;*/
height: 42px; */
display: none;
}
.three {
@ -417,10 +417,10 @@ suite('Tests for Toggle Comment action from Emmet (CSS)', () => {
const expectedContents = `
.one {
margin: 10px;
/*padding: 10px;
/* padding: 10px;
}
.two {
height: 42px;*/
height: 42px; */
display: none;
}
.three {
@ -445,10 +445,10 @@ suite('Tests for Toggle Comment action from Emmet (CSS)', () => {
const expectedContents = `
.one {
margin: 10px;
/*padding: 10px;
/* padding: 10px;
}
.two {
height: 42px;*/
height: 42px; */
display: none;
}
.three {
@ -473,10 +473,10 @@ suite('Tests for Toggle Comment action from Emmet (CSS)', () => {
const expectedContents = `
.one {
margin: 10px;
/*padding: 10px;
/* padding: 10px;
}
.two {
height: 42px;*/
height: 42px; */
display: none;
}
.three {
@ -500,16 +500,16 @@ suite('Tests for Toggle Comment action from Emmet (CSS)', () => {
test('toggle comment when multiple nodes of same parent are partially under single selection (CSS)', () => {
const expectedContents = `
.one {
/*margin: 10px;
padding: 10px;*/
/* margin: 10px;
padding: 10px; */
}
/*.two {
/* .two {
height: 42px;
display: none;
}
.three {
width: 42px;
*/ }`;
*/ }`;
return withRandomFileEditor(contents, 'css', (editor, doc) => {
editor.selections = [
new Selection(2, 7, 3, 10), // 2 properties partially under a single selection
@ -549,14 +549,14 @@ suite('Tests for Toggle Comment action from Emmet in nested css (SCSS)', () => {
test('toggle comment with multiple cursors selecting nested nodes (SCSS)', () => {
const expectedContents = `
.one {
/*height: 42px;*/
/* height: 42px; */
/*.two {
/* .two {
width: 42px;
}*/
} */
.three {
/*padding: 10px;*/
/* padding: 10px; */
}
}`;
return withRandomFileEditor(contents, 'css', (editor, doc) => {
@ -578,7 +578,7 @@ suite('Tests for Toggle Comment action from Emmet in nested css (SCSS)', () => {
});
test('toggle comment with multiple cursors selecting several nested nodes (SCSS)', () => {
const expectedContents = `
/*.one {
/* .one {
height: 42px;
.two {
@ -588,7 +588,7 @@ suite('Tests for Toggle Comment action from Emmet in nested css (SCSS)', () => {
.three {
padding: 10px;
}
}*/`;
} */`;
return withRandomFileEditor(contents, 'css', (editor, doc) => {
editor.selections = [
new Selection(1, 3, 1, 3), // cursor in the outside rule. And several cursors inside:
@ -611,14 +611,14 @@ suite('Tests for Toggle Comment action from Emmet in nested css (SCSS)', () => {
test('toggle comment with multiple cursors, but no selection (SCSS)', () => {
const expectedContents = `
.one {
/*height: 42px;*/
/* height: 42px; */
/*.two {
/* .two {
width: 42px;
}*/
} */
.three {
/*padding: 10px;*/
/* padding: 10px; */
}
}`;
return withRandomFileEditor(contents, 'css', (editor, doc) => {
@ -641,14 +641,14 @@ suite('Tests for Toggle Comment action from Emmet in nested css (SCSS)', () => {
test('toggle comment with multiple cursors and whole node selected (CSS)', () => {
const expectedContents = `
.one {
/*height: 42px;*/
/* height: 42px; */
/*.two {
/* .two {
width: 42px;
}*/
} */
.three {
/*padding: 10px;*/
/* padding: 10px; */
}
}`;
return withRandomFileEditor(contents, 'css', (editor, doc) => {
@ -673,11 +673,11 @@ suite('Tests for Toggle Comment action from Emmet in nested css (SCSS)', () => {
test('toggle comment when multiple nodes are completely under single selection (CSS)', () => {
const expectedContents = `
.one {
/*height: 42px;
/* height: 42px;
.two {
width: 42px;
}*/
} */
.three {
padding: 10px;
@ -701,11 +701,11 @@ suite('Tests for Toggle Comment action from Emmet in nested css (SCSS)', () => {
test('toggle comment when multiple nodes are partially under single selection (CSS)', () => {
const expectedContents = `
.one {
/*height: 42px;
/* height: 42px;
.two {
width: 42px;
*/ }
*/ }
.three {
padding: 10px;
@ -726,4 +726,29 @@ suite('Tests for Toggle Comment action from Emmet in nested css (SCSS)', () => {
});
});
});
test('toggle comment doesn\'t fail when start and end nodes differ HTML', () => {
const contents = `
<div>
<p>Hello</p>
</div>
`;
const expectedContents = `
<!-- <div>
<p>Hello</p>
</div> -->
`;
return withRandomFileEditor(contents, 'html', (editor, doc) => {
editor.selections = [
new Selection(1, 2, 2, 9), // <div> to <p> inclusive
];
return toggleComment().then(() => {
assert.equal(doc.getText(), expectedContents);
return toggleComment().then(() => {
assert.equal(doc.getText(), contents);
return Promise.resolve();
});
});
});
});
});

View file

@ -3,148 +3,147 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// import 'mocha';
// import * as assert from 'assert';
// import { Selection } from 'vscode';
// import { withRandomFileEditor, closeAllEditors } from './testUtils';
// import { updateImageSize } from '../updateImageSize';
import 'mocha';
import * as assert from 'assert';
import { Selection } from 'vscode';
import { withRandomFileEditor, closeAllEditors } from './testUtils';
import { updateImageSize } from '../updateImageSize';
// suite('Tests for Emmet actions on html tags', () => {
// teardown(closeAllEditors);
suite('Tests for Emmet actions on html tags', () => {
teardown(closeAllEditors);
// test('update image css with multiple cursors in css file', () => {
// const cssContents = `
// .one {
// margin: 10px;
// padding: 10px;
// background-image: url(https://github.com/microsoft/vscode/blob/master/resources/linux/code.png);
// }
// .two {
// background-image: url(https://github.com/microsoft/vscode/blob/master/resources/linux/code.png);
// height: 42px;
// }
// .three {
// background-image: url(https://github.com/microsoft/vscode/blob/master/resources/linux/code.png);
// width: 42px;
// }
// `;
// const expectedContents = `
// .one {
// margin: 10px;
// padding: 10px;
// background-image: url(https://github.com/microsoft/vscode/blob/master/resources/linux/code.png);
// width: 32px;
// height: 32px;
// }
// .two {
// background-image: url(https://github.com/microsoft/vscode/blob/master/resources/linux/code.png);
// width: 32px;
// height: 32px;
// }
// .three {
// background-image: url(https://github.com/microsoft/vscode/blob/master/resources/linux/code.png);
// height: 32px;
// width: 32px;
// }
// `;
// return withRandomFileEditor(cssContents, 'css', (editor, doc) => {
// editor.selections = [
// new Selection(4, 50, 4, 50),
// new Selection(7, 50, 7, 50),
// new Selection(11, 50, 11, 50)
// ];
test('update image css with multiple cursors in css file', () => {
const cssContents = `
.one {
margin: 10px;
padding: 10px;
background-image: url(https://raw.githubusercontent.com/microsoft/vscode/master/resources/linux/code.png);
}
.two {
background-image: url(https://raw.githubusercontent.com/microsoft/vscode/master/resources/linux/code.png);
height: 42px;
}
.three {
background-image: url(https://raw.githubusercontent.com/microsoft/vscode/master/resources/linux/code.png);
width: 42px;
}
`;
const expectedContents = `
.one {
margin: 10px;
padding: 10px;
background-image: url(https://raw.githubusercontent.com/microsoft/vscode/master/resources/linux/code.png);
width: 1024px;
height: 1024px;
}
.two {
background-image: url(https://raw.githubusercontent.com/microsoft/vscode/master/resources/linux/code.png);
width: 1024px;
height: 1024px;
}
.three {
background-image: url(https://raw.githubusercontent.com/microsoft/vscode/master/resources/linux/code.png);
height: 1024px;
width: 1024px;
}
`;
return withRandomFileEditor(cssContents, 'css', (editor, doc) => {
editor.selections = [
new Selection(4, 50, 4, 50),
new Selection(7, 50, 7, 50),
new Selection(11, 50, 11, 50)
];
// return updateImageSize()!.then(() => {
// assert.equal(doc.getText(), expectedContents);
// return Promise.resolve();
// });
// });
// });
return updateImageSize()!.then(() => {
assert.equal(doc.getText(), expectedContents);
return Promise.resolve();
});
});
});
// test('update image size in css in html file with multiple cursors', () => {
// const htmlWithCssContents = `
// <html>
// <style>
// .one {
// margin: 10px;
// padding: 10px;
// background-image: url(https://github.com/microsoft/vscode/blob/master/resources/linux/code.png);
// }
// .two {
// background-image: url(https://github.com/microsoft/vscode/blob/master/resources/linux/code.png);
// height: 42px;
// }
// .three {
// background-image: url(https://github.com/microsoft/vscode/blob/master/resources/linux/code.png);
// width: 42px;
// }
// </style>
// </html>
// `;
// const expectedContents = `
// <html>
// <style>
// .one {
// margin: 10px;
// padding: 10px;
// background-image: url(https://github.com/microsoft/vscode/blob/master/resources/linux/code.png);
// width: 32px;
// height: 32px;
// }
// .two {
// background-image: url(https://github.com/microsoft/vscode/blob/master/resources/linux/code.png);
// width: 32px;
// height: 32px;
// }
// .three {
// background-image: url(https://github.com/microsoft/vscode/blob/master/resources/linux/code.png);
// height: 32px;
// width: 32px;
// }
// </style>
// </html>
// `;
// return withRandomFileEditor(htmlWithCssContents, 'html', (editor, doc) => {
// editor.selections = [
// new Selection(6, 50, 6, 50),
// new Selection(9, 50, 9, 50),
// new Selection(13, 50, 13, 50)
// ];
test('update image size in css in html file with multiple cursors', () => {
const htmlWithCssContents = `
<html>
<style>
.one {
margin: 10px;
padding: 10px;
background-image: url(https://raw.githubusercontent.com/microsoft/vscode/master/resources/linux/code.png);
}
.two {
background-image: url(https://raw.githubusercontent.com/microsoft/vscode/master/resources/linux/code.png);
height: 42px;
}
.three {
background-image: url(https://raw.githubusercontent.com/microsoft/vscode/master/resources/linux/code.png);
width: 42px;
}
</style>
</html>
`;
const expectedContents = `
<html>
<style>
.one {
margin: 10px;
padding: 10px;
background-image: url(https://raw.githubusercontent.com/microsoft/vscode/master/resources/linux/code.png);
width: 1024px;
height: 1024px;
}
.two {
background-image: url(https://raw.githubusercontent.com/microsoft/vscode/master/resources/linux/code.png);
width: 1024px;
height: 1024px;
}
.three {
background-image: url(https://raw.githubusercontent.com/microsoft/vscode/master/resources/linux/code.png);
height: 1024px;
width: 1024px;
}
</style>
</html>
`;
return withRandomFileEditor(htmlWithCssContents, 'html', (editor, doc) => {
editor.selections = [
new Selection(6, 50, 6, 50),
new Selection(9, 50, 9, 50),
new Selection(13, 50, 13, 50)
];
// return updateImageSize()!.then(() => {
// assert.equal(doc.getText(), expectedContents);
// return Promise.resolve();
// });
// });
// });
return updateImageSize()!.then(() => {
assert.equal(doc.getText(), expectedContents);
return Promise.resolve();
});
});
});
// test('update image size in img tag in html file with multiple cursors', () => {
// const htmlwithimgtag = `
// <html>
// <img id="one" src="https://github.com/microsoft/vscode/blob/master/resources/linux/code.png" />
// <img id="two" src="https://github.com/microsoft/vscode/blob/master/resources/linux/code.png" width="56" />
// <img id="three" src="https://github.com/microsoft/vscode/blob/master/resources/linux/code.png" height="56" />
// </html>
// `;
// const expectedContents = `
// <html>
// <img id="one" src="https://github.com/microsoft/vscode/blob/master/resources/linux/code.png" width="32" height="32" />
// <img id="two" src="https://github.com/microsoft/vscode/blob/master/resources/linux/code.png" width="32" height="32" />
// <img id="three" src="https://github.com/microsoft/vscode/blob/master/resources/linux/code.png" height="32" width="32" />
// </html>
// `;
// return withRandomFileEditor(htmlwithimgtag, 'html', (editor, doc) => {
// editor.selections = [
// new Selection(2, 50, 2, 50),
// new Selection(3, 50, 3, 50),
// new Selection(4, 50, 4, 50)
// ];
test('update image size in img tag in html file with multiple cursors', () => {
const htmlwithimgtag = `
<html>
<img id="one" src="https://raw.githubusercontent.com/microsoft/vscode/master/resources/linux/code.png" />
<img id="two" src="https://raw.githubusercontent.com/microsoft/vscode/master/resources/linux/code.png" width="56" />
<img id="three" src="https://raw.githubusercontent.com/microsoft/vscode/master/resources/linux/code.png" height="56" />
</html>
`;
const expectedContents = `
<html>
<img id="one" src="https://raw.githubusercontent.com/microsoft/vscode/master/resources/linux/code.png" width="1024" height="1024" />
<img id="two" src="https://raw.githubusercontent.com/microsoft/vscode/master/resources/linux/code.png" width="1024" height="1024" />
<img id="three" src="https://raw.githubusercontent.com/microsoft/vscode/master/resources/linux/code.png" height="1024" width="1024" />
</html>
`;
return withRandomFileEditor(htmlwithimgtag, 'html', (editor, doc) => {
editor.selections = [
new Selection(2, 50, 2, 50),
new Selection(3, 50, 3, 50),
new Selection(4, 50, 4, 50)
];
// return updateImageSize()!.then(() => {
// assert.equal(doc.getText(), expectedContents);
// return Promise.resolve();
// });
// });
// });
// });
return updateImageSize()!.then(() => {
assert.equal(doc.getText(), expectedContents);
return Promise.resolve();
});
});
});
});

View file

@ -9,13 +9,20 @@ import { Selection, workspace, ConfigurationTarget } from 'vscode';
import { withRandomFileEditor, closeAllEditors } from './testUtils';
import { wrapWithAbbreviation, wrapIndividualLinesWithAbbreviation } from '../abbreviationActions';
const htmlContentsForWrapTests = `
const htmlContentsForBlockWrapTests = `
<ul class="nav main">
<li class="item1">img</li>
<li class="item2">$hithere</li>
</ul>
`;
const htmlContentsForInlineWrapTests = `
<ul class="nav main">
<em class="item1">img</em>
<em class="item2">$hithere</em>
</ul>
`;
const wrapBlockElementExpected = `
<ul class="nav main">
<div>
@ -29,15 +36,19 @@ const wrapBlockElementExpected = `
const wrapInlineElementExpected = `
<ul class="nav main">
<span><li class="item1">img</li></span>
<span><li class="item2">$hithere</li></span>
<span><em class="item1">img</em></span>
<span><em class="item2">$hithere</em></span>
</ul>
`;
const wrapSnippetExpected = `
<ul class="nav main">
<a href=""><li class="item1">img</li></a>
<a href=""><li class="item2">$hithere</li></a>
<a href="">
<li class="item1">img</li>
</a>
<a href="">
<li class="item2">$hithere</li>
</a>
</ul>
`;
@ -56,10 +67,16 @@ const wrapMultiLineAbbrExpected = `
</ul>
`;
// technically a bug, but also a feature (requested behaviour)
// https://github.com/microsoft/vscode/issues/78015
const wrapInlineElementExpectedFormatFalse = `
<ul class="nav main">
<h1><li class="item1">img</li></h1>
<h1><li class="item2">$hithere</li></h1>
<h1>
<li class="item1">img</li>
</h1>
<h1>
<li class="item2">$hithere</li>
</h1>
</ul>
`;
@ -73,51 +90,51 @@ suite('Tests for Wrap with Abbreviations', () => {
const oldValueForSyntaxProfiles = workspace.getConfiguration('emmet').inspect('syntaxProfile');
test('Wrap with block element using multi cursor', () => {
return testWrapWithAbbreviation(multiCursors, 'div', wrapBlockElementExpected);
return testWrapWithAbbreviation(multiCursors, 'div', wrapBlockElementExpected, htmlContentsForBlockWrapTests);
});
test('Wrap with inline element using multi cursor', () => {
return testWrapWithAbbreviation(multiCursors, 'span', wrapInlineElementExpected);
return testWrapWithAbbreviation(multiCursors, 'span', wrapInlineElementExpected, htmlContentsForInlineWrapTests);
});
test('Wrap with snippet using multi cursor', () => {
return testWrapWithAbbreviation(multiCursors, 'a', wrapSnippetExpected);
return testWrapWithAbbreviation(multiCursors, 'a', wrapSnippetExpected, htmlContentsForBlockWrapTests);
});
test('Wrap with multi line abbreviation using multi cursor', () => {
return testWrapWithAbbreviation(multiCursors, 'ul>li', wrapMultiLineAbbrExpected);
return testWrapWithAbbreviation(multiCursors, 'ul>li', wrapMultiLineAbbrExpected, htmlContentsForBlockWrapTests);
});
test('Wrap with block element using multi cursor selection', () => {
return testWrapWithAbbreviation(multiCursorsWithSelection, 'div', wrapBlockElementExpected);
return testWrapWithAbbreviation(multiCursorsWithSelection, 'div', wrapBlockElementExpected, htmlContentsForBlockWrapTests);
});
test('Wrap with inline element using multi cursor selection', () => {
return testWrapWithAbbreviation(multiCursorsWithSelection, 'span', wrapInlineElementExpected);
return testWrapWithAbbreviation(multiCursorsWithSelection, 'span', wrapInlineElementExpected, htmlContentsForInlineWrapTests);
});
test('Wrap with snippet using multi cursor selection', () => {
return testWrapWithAbbreviation(multiCursorsWithSelection, 'a', wrapSnippetExpected);
return testWrapWithAbbreviation(multiCursorsWithSelection, 'a', wrapSnippetExpected, htmlContentsForBlockWrapTests);
});
test('Wrap with multi line abbreviation using multi cursor selection', () => {
return testWrapWithAbbreviation(multiCursorsWithSelection, 'ul>li', wrapMultiLineAbbrExpected);
return testWrapWithAbbreviation(multiCursorsWithSelection, 'ul>li', wrapMultiLineAbbrExpected, htmlContentsForBlockWrapTests);
});
test('Wrap with block element using multi cursor full line selection', () => {
return testWrapWithAbbreviation(multiCursorsWithFullLineSelection, 'div', wrapBlockElementExpected);
return testWrapWithAbbreviation(multiCursorsWithFullLineSelection, 'div', wrapBlockElementExpected, htmlContentsForBlockWrapTests);
});
test('Wrap with inline element using multi cursor full line selection', () => {
return testWrapWithAbbreviation(multiCursorsWithFullLineSelection, 'span', wrapInlineElementExpected);
return testWrapWithAbbreviation(multiCursorsWithFullLineSelection, 'span', wrapInlineElementExpected, htmlContentsForInlineWrapTests);
});
test('Wrap with snippet using multi cursor full line selection', () => {
return testWrapWithAbbreviation(multiCursorsWithFullLineSelection, 'a', wrapSnippetExpected);
return testWrapWithAbbreviation(multiCursorsWithFullLineSelection, 'a', wrapSnippetExpected, htmlContentsForBlockWrapTests);
});
test('Wrap with multi line abbreviation using multi cursor full line selection', () => {
return testWrapWithAbbreviation(multiCursorsWithFullLineSelection, 'ul>li', wrapMultiLineAbbrExpected);
return testWrapWithAbbreviation(multiCursorsWithFullLineSelection, 'ul>li', wrapMultiLineAbbrExpected, htmlContentsForBlockWrapTests);
});
test('Wrap with abbreviation and comment filter', () => {
@ -128,15 +145,31 @@ suite('Tests for Wrap with Abbreviations', () => {
`;
const expectedContents = `
<ul class="nav main">
<li class="hello">
line
</li>
<li class="hello">line</li>
<!-- /.hello -->
</ul>
`;
return testWrapWithAbbreviation([new Selection(2, 0, 2, 0)], 'li.hello|c', expectedContents, contents);
});
test('Wrap with abbreviation link', () => {
const contents = `
<ul class="nav main">
line
</ul>
`;
const expectedContents = `
<a href="https://example.com">
<div>
<ul class="nav main">
line
</ul>
</div>
</a>
`;
return testWrapWithAbbreviation([new Selection(1, 1, 1, 1)], 'a[href="https://example.com"]>div', expectedContents, contents);
});
test('Wrap with abbreviation entire node when cursor is on opening tag', () => {
const contents = `
<div class="nav main">
@ -192,8 +225,12 @@ suite('Tests for Wrap with Abbreviations', () => {
const wrapIndividualLinesExpected = `
<ul class="nav main">
<ul>
<li class="hello1"><li class="item1">This $10 is not a tabstop</li></li>
<li class="hello2"><li class="item2">hi.there</li></li>
<li class="hello1">
<li class="item1">This $10 is not a tabstop</li>
</li>
<li class="hello2">
<li class="item2">hi.there</li>
</li>
</ul>
</ul>
`;
@ -210,8 +247,12 @@ suite('Tests for Wrap with Abbreviations', () => {
const wrapIndividualLinesExpected = `
<ul class="nav main">
<ul>
<li class="hello1"><li class="item1">img</li></li>
<li class="hello2"><li class="item2">hi.there</li></li>
<li class="hello1">
<li class="item1">img</li>
</li>
<li class="hello2">
<li class="item2">hi.there</li>
</li>
</ul>
</ul>
`;
@ -228,9 +269,13 @@ suite('Tests for Wrap with Abbreviations', () => {
const wrapIndividualLinesExpected = `
<ul class="nav main">
<ul>
<li class="hello"><li class="item1">img</li></li>
<li class="hello">
<li class="item1">img</li>
</li>
<!-- /.hello -->
<li class="hello"><li class="item2">hi.there</li></li>
<li class="hello">
<li class="item2">hi.there</li>
</li>
<!-- /.hello -->
</ul>
</ul>
@ -257,9 +302,9 @@ suite('Tests for Wrap with Abbreviations', () => {
});
test('Wrap with abbreviation and format set to false', () => {
return workspace.getConfiguration('emmet').update('syntaxProfiles',{ 'html' : { 'format': false } } , ConfigurationTarget.Global).then(() => {
return testWrapWithAbbreviation(multiCursors,'h1',wrapInlineElementExpectedFormatFalse).then(() => {
return workspace.getConfiguration('emmet').update('syntaxProfiles',oldValueForSyntaxProfiles ? oldValueForSyntaxProfiles.globalValue : undefined, ConfigurationTarget.Global);
return workspace.getConfiguration('emmet').update('syntaxProfiles', { 'html' : { 'format': false } }, ConfigurationTarget.Global).then(() => {
return testWrapWithAbbreviation(multiCursors, 'h1', wrapInlineElementExpectedFormatFalse, htmlContentsForBlockWrapTests).then(() => {
return workspace.getConfiguration('emmet').update('syntaxProfiles', oldValueForSyntaxProfiles ? oldValueForSyntaxProfiles.globalValue : undefined, ConfigurationTarget.Global);
});
});
});
@ -302,23 +347,27 @@ suite('Tests for Wrap with Abbreviations', () => {
</ul>
`;
return testWrapWithAbbreviation([new Selection(2,2,3,33)], '.hello', wrapMultiLineJsxExpected, htmlContentsForWrapTests, 'jsx');
return testWrapWithAbbreviation([new Selection(2,2,3,33)], '.hello', wrapMultiLineJsxExpected, htmlContentsForBlockWrapTests, 'jsx');
});
test('Wrap individual line with abbreviation uses className for jsx files', () => {
const wrapIndividualLinesJsxExpected = `
<ul class="nav main">
<div className="hello1"><li class="item1">img</li></div>
<div className="hello2"><li class="item2">$hithere</li></div>
<div className="hello1">
<li class="item1">img</li>
</div>
<div className="hello2">
<li class="item2">$hithere</li>
</div>
</ul>
`;
return testWrapIndividualLinesWithAbbreviation([new Selection(2,2,3,33)], '.hello$*', wrapIndividualLinesJsxExpected, htmlContentsForWrapTests, 'jsx');
return testWrapIndividualLinesWithAbbreviation([new Selection(2,2,3,33)], '.hello$*', wrapIndividualLinesJsxExpected, htmlContentsForBlockWrapTests, 'jsx');
});
});
function testWrapWithAbbreviation(selections: Selection[], abbreviation: string, expectedContents: string, input: string = htmlContentsForWrapTests, fileExtension: string = 'html'): Thenable<any> {
function testWrapWithAbbreviation(selections: Selection[], abbreviation: string, expectedContents: string, input: string, fileExtension: string = 'html'): Thenable<any> {
return withRandomFileEditor(input, fileExtension, (editor, _) => {
editor.selections = selections;
const promise = wrapWithAbbreviation({ abbreviation });
@ -334,7 +383,7 @@ function testWrapWithAbbreviation(selections: Selection[], abbreviation: string,
});
}
function testWrapIndividualLinesWithAbbreviation(selections: Selection[], abbreviation: string, expectedContents: string, input: string = htmlContentsForWrapTests, fileExtension: string = 'html'): Thenable<any> {
function testWrapIndividualLinesWithAbbreviation(selections: Selection[], abbreviation: string, expectedContents: string, input: string, fileExtension: string = 'html'): Thenable<any> {
return withRandomFileEditor(input, fileExtension, (editor, _) => {
editor.selections = selections;
const promise = wrapIndividualLinesWithAbbreviation({ abbreviation });

View file

@ -4,22 +4,24 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { getNodesInBetween, getNode, getHtmlNode, parseDocument, sameNodes, isStyleSheet, validate } from './util';
import { Node, Stylesheet, Rule } from 'EmmetNode';
import { getNodesInBetween, getFlatNode, getHtmlFlatNode, sameNodes, isStyleSheet, validate, offsetRangeToVsRange, offsetRangeToSelection } from './util';
import { Node, Stylesheet, Rule } from 'EmmetFlatNode';
import parseStylesheet from '@emmetio/css-parser';
import { DocumentStreamReader } from './bufferStream';
import { getRootNode } from './parseDocument';
const startCommentStylesheet = '/*';
const endCommentStylesheet = '*/';
const startCommentHTML = '<!--';
const endCommentHTML = '-->';
let startCommentStylesheet: string;
let endCommentStylesheet: string;
let startCommentHTML: string;
let endCommentHTML: string;
export function toggleComment(): Thenable<boolean> | undefined {
if (!validate() || !vscode.window.activeTextEditor) {
return;
}
setupCommentSpacing();
const editor = vscode.window.activeTextEditor;
let rootNode = parseDocument(editor.document);
const rootNode = getRootNode(editor.document, true);
if (!rootNode) {
return;
}
@ -27,7 +29,7 @@ export function toggleComment(): Thenable<boolean> | undefined {
return editor.edit(editBuilder => {
let allEdits: vscode.TextEdit[][] = [];
editor.selections.reverse().forEach(selection => {
let edits = isStyleSheet(editor.document.languageId) ? toggleCommentStylesheet(selection, <Stylesheet>rootNode) : toggleCommentHTML(editor.document, selection, rootNode!);
const edits = isStyleSheet(editor.document.languageId) ? toggleCommentStylesheet(editor.document, selection, <Stylesheet>rootNode) : toggleCommentHTML(editor.document, selection, rootNode!);
if (edits.length > 0) {
allEdits.push(edits);
}
@ -53,21 +55,25 @@ export function toggleComment(): Thenable<boolean> | undefined {
function toggleCommentHTML(document: vscode.TextDocument, selection: vscode.Selection, rootNode: Node): vscode.TextEdit[] {
const selectionStart = selection.isReversed ? selection.active : selection.anchor;
const selectionEnd = selection.isReversed ? selection.anchor : selection.active;
const selectionStartOffset = document.offsetAt(selectionStart);
const selectionEndOffset = document.offsetAt(selectionEnd);
const documentText = document.getText();
let startNode = getHtmlNode(document, rootNode, selectionStart, true);
let endNode = getHtmlNode(document, rootNode, selectionEnd, true);
const startNode = getHtmlFlatNode(documentText, rootNode, selectionStartOffset, true);
const endNode = getHtmlFlatNode(documentText, rootNode, selectionEndOffset, true);
if (!startNode || !endNode) {
return [];
}
if (sameNodes(startNode, endNode) && startNode.name === 'style'
&& startNode.open.end.isBefore(selectionStart)
&& startNode.close.start.isAfter(selectionEnd)) {
let buffer = new DocumentStreamReader(document, startNode.open.end, new vscode.Range(startNode.open.end, startNode.close.start));
let cssRootNode = parseStylesheet(buffer);
return toggleCommentStylesheet(selection, cssRootNode);
&& startNode.open && startNode.close
&& startNode.open.end < selectionStartOffset
&& startNode.close.start > selectionEndOffset) {
const buffer = ' '.repeat(startNode.open.end) +
documentText.substring(startNode.open.end, startNode.close.start);
const cssRootNode = parseStylesheet(buffer);
return toggleCommentStylesheet(document, selection, cssRootNode);
}
let allNodes: Node[] = getNodesInBetween(startNode, endNode);
@ -82,8 +88,8 @@ function toggleCommentHTML(document: vscode.TextDocument, selection: vscode.Sele
}
edits.push(new vscode.TextEdit(new vscode.Range(allNodes[0].start, allNodes[0].start), startCommentHTML));
edits.push(new vscode.TextEdit(new vscode.Range(allNodes[allNodes.length - 1].end, allNodes[allNodes.length - 1].end), endCommentHTML));
edits.push(new vscode.TextEdit(offsetRangeToVsRange(document, allNodes[0].start, allNodes[0].start), startCommentHTML));
edits.push(new vscode.TextEdit(offsetRangeToVsRange(document, allNodes[allNodes.length - 1].end, allNodes[allNodes.length - 1].end), endCommentHTML));
return edits;
}
@ -93,10 +99,8 @@ function getRangesToUnCommentHTML(node: Node, document: vscode.TextDocument): vs
// If current node is commented, then uncomment and return
if (node.type === 'comment') {
unCommentTextEdits.push(new vscode.TextEdit(new vscode.Range(node.start, node.start.translate(0, startCommentHTML.length)), ''));
unCommentTextEdits.push(new vscode.TextEdit(new vscode.Range(node.end.translate(0, -endCommentHTML.length), node.end), ''));
unCommentTextEdits.push(new vscode.TextEdit(offsetRangeToVsRange(document, node.start, node.start + startCommentHTML.length), ''));
unCommentTextEdits.push(new vscode.TextEdit(offsetRangeToVsRange(document, node.end - endCommentHTML.length, node.end), ''));
return unCommentTextEdits;
}
@ -108,32 +112,34 @@ function getRangesToUnCommentHTML(node: Node, document: vscode.TextDocument): vs
return unCommentTextEdits;
}
function toggleCommentStylesheet(selection: vscode.Selection, rootNode: Stylesheet): vscode.TextEdit[] {
let selectionStart = selection.isReversed ? selection.active : selection.anchor;
let selectionEnd = selection.isReversed ? selection.anchor : selection.active;
function toggleCommentStylesheet(document: vscode.TextDocument, selection: vscode.Selection, rootNode: Stylesheet): vscode.TextEdit[] {
const selectionStart = selection.isReversed ? selection.active : selection.anchor;
const selectionEnd = selection.isReversed ? selection.anchor : selection.active;
let selectionStartOffset = document.offsetAt(selectionStart);
let selectionEndOffset = document.offsetAt(selectionEnd);
let startNode = getNode(rootNode, selectionStart, true);
let endNode = getNode(rootNode, selectionEnd, true);
const startNode = getFlatNode(rootNode, selectionStartOffset, true);
const endNode = getFlatNode(rootNode, selectionEndOffset, true);
if (!selection.isEmpty) {
selectionStart = adjustStartNodeCss(startNode, selectionStart, rootNode);
selectionEnd = adjustEndNodeCss(endNode, selectionEnd, rootNode);
selection = new vscode.Selection(selectionStart, selectionEnd);
selectionStartOffset = adjustStartNodeCss(startNode, selectionStartOffset, rootNode);
selectionEndOffset = adjustEndNodeCss(endNode, selectionEndOffset, rootNode);
selection = offsetRangeToSelection(document, selectionStartOffset, selectionEndOffset);
} else if (startNode) {
selectionStart = startNode.start;
selectionEnd = startNode.end;
selection = new vscode.Selection(selectionStart, selectionEnd);
selectionStartOffset = startNode.start;
selectionEndOffset = startNode.end;
selection = offsetRangeToSelection(document, selectionStartOffset, selectionEndOffset);
}
// Uncomment the comments that intersect with the selection.
let rangesToUnComment: vscode.Range[] = [];
let edits: vscode.TextEdit[] = [];
rootNode.comments.forEach(comment => {
let commentRange = new vscode.Range(comment.start, comment.end);
const commentRange = offsetRangeToVsRange(document, comment.start, comment.end);
if (selection.intersection(commentRange)) {
rangesToUnComment.push(commentRange);
edits.push(new vscode.TextEdit(new vscode.Range(comment.start, comment.start.translate(0, startCommentStylesheet.length)), ''));
edits.push(new vscode.TextEdit(new vscode.Range(comment.end.translate(0, -endCommentStylesheet.length), comment.end), ''));
edits.push(new vscode.TextEdit(offsetRangeToVsRange(document, comment.start, comment.start + startCommentStylesheet.length), ''));
edits.push(new vscode.TextEdit(offsetRangeToVsRange(document, comment.end - endCommentStylesheet.length, comment.end), ''));
}
});
@ -145,20 +151,32 @@ function toggleCommentStylesheet(selection: vscode.Selection, rootNode: Styleshe
new vscode.TextEdit(new vscode.Range(selection.start, selection.start), startCommentStylesheet),
new vscode.TextEdit(new vscode.Range(selection.end, selection.end), endCommentStylesheet)
];
}
function adjustStartNodeCss(node: Node | null, pos: vscode.Position, rootNode: Stylesheet): vscode.Position {
function setupCommentSpacing() {
const config: boolean | undefined = vscode.workspace.getConfiguration('editor.comments').get('insertSpace');
if (config) {
startCommentStylesheet = '/* ';
endCommentStylesheet = ' */';
startCommentHTML = '<!-- ';
endCommentHTML = ' -->';
} else {
startCommentStylesheet = '/*';
endCommentStylesheet = '*/';
startCommentHTML = '<!--';
endCommentHTML = '-->';
}
}
function adjustStartNodeCss(node: Node | undefined, offset: number, rootNode: Stylesheet): number {
for (const comment of rootNode.comments) {
let commentRange = new vscode.Range(comment.start, comment.end);
if (commentRange.contains(pos)) {
return pos;
if (comment.start <= offset && offset <= comment.end) {
return offset;
}
}
if (!node) {
return pos;
return offset;
}
if (node.type === 'property') {
@ -166,32 +184,31 @@ function adjustStartNodeCss(node: Node | null, pos: vscode.Position, rootNode: S
}
const rule = <Rule>node;
if (pos.isBefore(rule.contentStartToken.end) || !rule.firstChild) {
if (offset < rule.contentStartToken.end || !rule.firstChild) {
return rule.start;
}
if (pos.isBefore(rule.firstChild.start)) {
return pos;
if (offset < rule.firstChild.start) {
return offset;
}
let newStartNode = rule.firstChild;
while (newStartNode.nextSibling && pos.isAfter(newStartNode.end)) {
while (newStartNode.nextSibling && offset > newStartNode.end) {
newStartNode = newStartNode.nextSibling;
}
return newStartNode.start;
}
function adjustEndNodeCss(node: Node | null, pos: vscode.Position, rootNode: Stylesheet): vscode.Position {
function adjustEndNodeCss(node: Node | undefined, offset: number, rootNode: Stylesheet): number {
for (const comment of rootNode.comments) {
let commentRange = new vscode.Range(comment.start, comment.end);
if (commentRange.contains(pos)) {
return pos;
if (comment.start <= offset && offset <= comment.end) {
return offset;
}
}
if (!node) {
return pos;
return offset;
}
if (node.type === 'property') {
@ -199,16 +216,16 @@ function adjustEndNodeCss(node: Node | null, pos: vscode.Position, rootNode: Sty
}
const rule = <Rule>node;
if (pos.isEqual(rule.contentEndToken.end) || !rule.firstChild) {
if (offset === rule.contentEndToken.end || !rule.firstChild) {
return rule.end;
}
if (pos.isAfter(rule.children[rule.children.length - 1].end)) {
return pos;
if (offset > rule.children[rule.children.length - 1].end) {
return offset;
}
let newEndNode = rule.children[rule.children.length - 1];
while (newEndNode.previousSibling && pos.isBefore(newEndNode.start)) {
while (newEndNode.previousSibling && offset < newEndNode.start) {
newEndNode = newEndNode.previousSibling;
}

View file

@ -0,0 +1,89 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
declare module 'EmmetFlatNode' {
export interface Node {
start: number
end: number
type: string
parent: Node | undefined
firstChild: Node | undefined
nextSibling: Node | undefined
previousSibling: Node | undefined
children: Node[]
}
export interface Token {
start: number
end: number
stream: BufferStream
toString(): string
}
export interface CssToken extends Token {
size: number
item(number: number): any
type: string
}
export interface HtmlToken extends Token {
value: string
}
export interface Attribute extends Token {
name: Token
value: Token
}
export interface HtmlNode extends Node {
name: string
open: Token | undefined
close: Token | undefined
parent: HtmlNode | undefined
firstChild: HtmlNode | undefined
nextSibling: HtmlNode | undefined
previousSibling: HtmlNode | undefined
children: HtmlNode[]
attributes: Attribute[]
}
export interface CssNode extends Node {
name: string
parent: CssNode | undefined
firstChild: CssNode | undefined
nextSibling: CssNode | undefined
previousSibling: CssNode | undefined
children: CssNode[]
}
export interface Rule extends CssNode {
selectorToken: Token
contentStartToken: Token
contentEndToken: Token
}
export interface Property extends CssNode {
valueToken: Token
separator: string
parent: Rule
terminatorToken: Token
separatorToken: Token
value: string
}
export interface Stylesheet extends Node {
comments: Token[]
}
export interface BufferStream {
peek(): number
next(): number
backUp(n: number): number
current(): string
substring(from: number, to: number): string
eat(match: any): boolean
eatWhile(match: any): boolean
}
}

View file

@ -5,8 +5,10 @@
declare module '@emmetio/css-parser' {
import { BufferStream, Stylesheet } from 'EmmetNode';
import { Stylesheet as FlatStylesheet } from 'EmmetFlatNode';
function parseStylesheet(stream: BufferStream): Stylesheet;
function parseStylesheet(stream: string): FlatStylesheet;
export default parseStylesheet;
}

View file

@ -5,8 +5,10 @@
declare module '@emmetio/html-matcher' {
import { BufferStream, HtmlNode } from 'EmmetNode';
import { HtmlNode as HtmlFlatNode } from 'EmmetFlatNode';
function parse(stream: BufferStream): HtmlNode;
function parse(stream: string): HtmlFlatNode;
export default parse;
}

View file

@ -5,26 +5,26 @@
// Based on @sergeche's work on the emmet plugin for atom
import { TextEditor, Range, Position, window, TextEdit } from 'vscode';
import { TextEditor, Position, window, TextEdit } from 'vscode';
import * as path from 'path';
import { getImageSize } from './imageSizeHelper';
import { parseDocument, getNode, iterateCSSToken, getCssPropertyFromRule, isStyleSheet, validate } from './util';
import { HtmlNode, CssToken, HtmlToken, Attribute, Property } from 'EmmetNode';
import { getFlatNode, iterateCSSToken, getCssPropertyFromRule, isStyleSheet, validate, offsetRangeToVsRange } from './util';
import { HtmlNode, CssToken, HtmlToken, Attribute, Property } from 'EmmetFlatNode';
import { locateFile } from './locateFile';
import parseStylesheet from '@emmetio/css-parser';
import { DocumentStreamReader } from './bufferStream';
import { getRootNode } from './parseDocument';
/**
* Updates size of context image in given editor
*/
export function updateImageSize() {
export function updateImageSize(): Promise<boolean> | undefined {
if (!validate() || !window.activeTextEditor) {
return;
}
const editor = window.activeTextEditor;
let allUpdatesPromise = editor.selections.reverse().map(selection => {
let position = selection.isReversed ? selection.active : selection.anchor;
const allUpdatesPromise = editor.selections.reverse().map(selection => {
const position = selection.isReversed ? selection.active : selection.anchor;
if (!isStyleSheet(editor.document.languageId)) {
return updateImageSizeHTML(editor, position);
} else {
@ -71,15 +71,19 @@ function updateImageSizeHTML(editor: TextEditor, position: Position): Promise<Te
function updateImageSizeStyleTag(editor: TextEditor, position: Position): Promise<TextEdit[]> {
const getPropertyInsiderStyleTag = (editor: TextEditor): Property | null => {
const rootNode = parseDocument(editor.document);
const currentNode = <HtmlNode>getNode(rootNode, position, true);
const document = editor.document;
const rootNode = getRootNode(document, true);
const offset = document.offsetAt(position);
const currentNode = <HtmlNode>getFlatNode(rootNode, offset, true);
if (currentNode && currentNode.name === 'style'
&& currentNode.open.end.isBefore(position)
&& currentNode.close.start.isAfter(position)) {
let buffer = new DocumentStreamReader(editor.document, currentNode.open.end, new Range(currentNode.open.end, currentNode.close.start));
let rootNode = parseStylesheet(buffer);
const node = getNode(rootNode, position, true);
return (node && node.type === 'property') ? <Property>node : null;
&& currentNode.open && currentNode.close
&& currentNode.open.end < offset
&& currentNode.close.start > offset) {
const buffer = ' '.repeat(currentNode.open.end) +
document.getText().substring(currentNode.open.end, currentNode.close.start);
const innerRootNode = parseStylesheet(buffer);
const innerNode = getFlatNode(innerRootNode, offset, true);
return (innerNode && innerNode.type === 'property') ? <Property>innerNode : null;
}
return null;
};
@ -96,7 +100,7 @@ function updateImageSizeCSSFile(editor: TextEditor, position: Position): Promise
*/
function updateImageSizeCSS(editor: TextEditor, position: Position, fetchNode: (editor: TextEditor, position: Position) => Property | null): Promise<TextEdit[]> {
const node = fetchNode(editor, position);
const src = node && getImageSrcCSS(node, position);
const src = node && getImageSrcCSS(editor, node, position);
if (!src) {
return Promise.reject(new Error('No valid image source'));
@ -108,7 +112,7 @@ function updateImageSizeCSS(editor: TextEditor, position: Position, fetchNode: (
// since this action is asynchronous, we have to ensure that editor wasnt
// changed and user didnt moved caret outside <img> node
const prop = fetchNode(editor, position);
if (prop && getImageSrcCSS(prop, position) === src) {
if (prop && getImageSrcCSS(editor, prop, position) === src) {
return updateCSSNode(editor, prop, size.width, size.height);
}
return [];
@ -121,8 +125,10 @@ function updateImageSizeCSS(editor: TextEditor, position: Position, fetchNode: (
* be found
*/
function getImageHTMLNode(editor: TextEditor, position: Position): HtmlNode | null {
const rootNode = parseDocument(editor.document);
const node = <HtmlNode>getNode(rootNode, position, true);
const document = editor.document;
const rootNode = getRootNode(document, true);
const offset = document.offsetAt(position);
const node = <HtmlNode>getFlatNode(rootNode, offset, true);
return node && node.name.toLowerCase() === 'img' ? node : null;
}
@ -132,8 +138,10 @@ function getImageHTMLNode(editor: TextEditor, position: Position): HtmlNode | nu
* be found
*/
function getImageCSSNode(editor: TextEditor, position: Position): Property | null {
const rootNode = parseDocument(editor.document);
const node = getNode(rootNode, position, true);
const document = editor.document;
const rootNode = getRootNode(document, true);
const offset = document.offsetAt(position);
const node = getFlatNode(rootNode, offset, true);
return node && node.type === 'property' ? <Property>node : null;
}
@ -152,11 +160,11 @@ function getImageSrcHTML(node: HtmlNode): string | undefined {
/**
* Returns image source from given `url()` token
*/
function getImageSrcCSS(node: Property | undefined, position: Position): string | undefined {
function getImageSrcCSS(editor: TextEditor, node: Property | undefined, position: Position): string | undefined {
if (!node) {
return;
}
const urlToken = findUrlToken(node, position);
const urlToken = findUrlToken(editor, node, position);
if (!urlToken) {
return;
}
@ -174,7 +182,12 @@ function getImageSrcCSS(node: Property | undefined, position: Position): string
* Updates size of given HTML node
*/
function updateHTMLTag(editor: TextEditor, node: HtmlNode, width: number, height: number): TextEdit[] {
const document = editor.document;
const srcAttr = getAttribute(node, 'src');
if (!srcAttr) {
return [];
}
const widthAttr = getAttribute(node, 'width');
const heightAttr = getAttribute(node, 'height');
const quote = getAttributeQuote(editor, srcAttr);
@ -186,15 +199,15 @@ function updateHTMLTag(editor: TextEditor, node: HtmlNode, width: number, height
if (!widthAttr) {
textToAdd += ` width=${quote}${width}${quote}`;
} else {
edits.push(new TextEdit(new Range(widthAttr.value.start, widthAttr.value.end), String(width)));
edits.push(new TextEdit(offsetRangeToVsRange(document, widthAttr.value.start, widthAttr.value.end), String(width)));
}
if (!heightAttr) {
textToAdd += ` height=${quote}${height}${quote}`;
} else {
edits.push(new TextEdit(new Range(heightAttr.value.start, heightAttr.value.end), String(height)));
edits.push(new TextEdit(offsetRangeToVsRange(document, heightAttr.value.start, heightAttr.value.end), String(height)));
}
if (textToAdd) {
edits.push(new TextEdit(new Range(endOfAttributes, endOfAttributes), textToAdd));
edits.push(new TextEdit(offsetRangeToVsRange(document, endOfAttributes, endOfAttributes), textToAdd));
}
return edits;
@ -204,6 +217,7 @@ function updateHTMLTag(editor: TextEditor, node: HtmlNode, width: number, height
* Updates size of given CSS rule
*/
function updateCSSNode(editor: TextEditor, srcProp: Property, width: number, height: number): TextEdit[] {
const document = editor.document;
const rule = srcProp.parent;
const widthProp = getCssPropertyFromRule(rule, 'width');
const heightProp = getCssPropertyFromRule(rule, 'height');
@ -214,22 +228,22 @@ function updateCSSNode(editor: TextEditor, srcProp: Property, width: number, hei
let edits: TextEdit[] = [];
if (!srcProp.terminatorToken) {
edits.push(new TextEdit(new Range(srcProp.end, srcProp.end), ';'));
edits.push(new TextEdit(offsetRangeToVsRange(document, srcProp.end, srcProp.end), ';'));
}
let textToAdd = '';
if (!widthProp) {
textToAdd += `${before}width${separator}${width}px;`;
} else {
edits.push(new TextEdit(new Range(widthProp.valueToken.start, widthProp.valueToken.end), `${width}px`));
edits.push(new TextEdit(offsetRangeToVsRange(document, widthProp.valueToken.start, widthProp.valueToken.end), `${width}px`));
}
if (!heightProp) {
textToAdd += `${before}height${separator}${height}px;`;
} else {
edits.push(new TextEdit(new Range(heightProp.valueToken.start, heightProp.valueToken.end), `${height}px`));
edits.push(new TextEdit(offsetRangeToVsRange(document, heightProp.valueToken.start, heightProp.valueToken.end), `${height}px`));
}
if (textToAdd) {
edits.push(new TextEdit(new Range(srcProp.end, srcProp.end), textToAdd));
edits.push(new TextEdit(offsetRangeToVsRange(document, srcProp.end, srcProp.end), textToAdd));
}
return edits;
@ -238,9 +252,9 @@ function updateCSSNode(editor: TextEditor, srcProp: Property, width: number, hei
/**
* Returns attribute object with `attrName` name from given HTML node
*/
function getAttribute(node: HtmlNode, attrName: string): Attribute {
function getAttribute(node: HtmlNode, attrName: string): Attribute | undefined {
attrName = attrName.toLowerCase();
return node && (node.open as any).attributes.find((attr: any) => attr.name.value.toLowerCase() === attrName);
return node && node.attributes.find(attr => attr.name.toString().toLowerCase() === attrName);
}
/**
@ -248,18 +262,20 @@ function getAttribute(node: HtmlNode, attrName: string): Attribute {
* string if attribute wasnt quoted
*/
function getAttributeQuote(editor: TextEditor, attr: any): string {
const range = new Range(attr.value ? attr.value.end : attr.end, attr.end);
return range.isEmpty ? '' : editor.document.getText(range);
function getAttributeQuote(editor: TextEditor, attr: Attribute): string {
const begin = attr.value ? attr.value.end : attr.end;
const end = attr.end;
return begin === end ? '' : editor.document.getText().substring(begin, end);
}
/**
* Finds 'url' token for given `pos` point in given CSS property `node`
*/
function findUrlToken(node: Property, pos: Position): CssToken | undefined {
function findUrlToken(editor: TextEditor, node: Property, pos: Position): CssToken | undefined {
const offset = editor.document.offsetAt(pos);
for (let i = 0, il = (node as any).parsedValue.length, url; i < il; i++) {
iterateCSSToken((node as any).parsedValue[i], (token: CssToken) => {
if (token.type === 'url' && token.start.isBeforeOrEqual(pos) && token.end.isAfterOrEqual(pos)) {
if (token.type === 'url' && token.start <= offset && token.end >= offset) {
url = token;
return false;
}
@ -279,9 +295,9 @@ function findUrlToken(node: Property, pos: Position): CssToken | undefined {
function getPropertyDelimitor(editor: TextEditor, node: Property): string {
let anchor;
if (anchor = (node.previousSibling || node.parent.contentStartToken)) {
return editor.document.getText(new Range(anchor.end, node.start));
return editor.document.getText().substring(anchor.end, node.start);
} else if (anchor = (node.nextSibling || node.parent.contentEndToken)) {
return editor.document.getText(new Range(node.end, anchor.start));
return editor.document.getText().substring(node.end, anchor.start);
}
return '';

View file

@ -4,8 +4,9 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { getHtmlNodeLS, toLSTextDocument, validate } from './util';
import { TextDocument as LSTextDocument, Node as LSNode } from 'vscode-html-languageservice';
import { getHtmlFlatNode, validate } from './util';
import { HtmlNode as HtmlFlatNode } from 'EmmetFlatNode';
import { getRootNode } from './parseDocument';
export function updateTag(tagName: string): Thenable<boolean> | undefined {
if (!validate(false) || !vscode.window.activeTextEditor) {
@ -13,9 +14,15 @@ export function updateTag(tagName: string): Thenable<boolean> | undefined {
}
const editor = vscode.window.activeTextEditor;
const document = editor.document;
const rootNode = <HtmlFlatNode>getRootNode(document, true);
if (!rootNode) {
return;
}
const rangesToUpdate = editor.selections.reverse()
.reduce<vscode.Range[]>((prev, selection) =>
prev.concat(getRangesToUpdate(editor, selection)), []);
prev.concat(getRangesToUpdate(document, selection, rootNode)), []);
return editor.edit(editBuilder => {
rangesToUpdate.forEach(range => {
@ -24,34 +31,25 @@ export function updateTag(tagName: string): Thenable<boolean> | undefined {
});
}
function getPositionFromOffset(offset: number | undefined, document: LSTextDocument): vscode.Position | undefined {
if (offset === undefined) {
return undefined;
}
const pos = document.positionAt(offset);
return new vscode.Position(pos.line, pos.character);
}
function getRangesFromNode(node: LSNode, document: LSTextDocument): vscode.Range[] {
const start = getPositionFromOffset(node.start, document)!;
const startTagEnd = getPositionFromOffset(node.startTagEnd, document);
const end = getPositionFromOffset(node.end, document)!;
const endTagStart = getPositionFromOffset(node.endTagStart, document);
function getRangesFromNode(node: HtmlFlatNode, document: vscode.TextDocument): vscode.Range[] {
let ranges: vscode.Range[] = [];
if (startTagEnd) {
if (node.open) {
const start = document.positionAt(node.open.start);
ranges.push(new vscode.Range(start.translate(0, 1),
start.translate(0, 1).translate(0, node.tag!.length ?? 0)));
start.translate(0, 1).translate(0, node.name.length)));
}
if (endTagStart) {
if (node.close) {
const endTagStart = document.positionAt(node.close.start);
const end = document.positionAt(node.close.end);
ranges.push(new vscode.Range(endTagStart.translate(0, 2), end.translate(0, -1)));
}
return ranges;
}
function getRangesToUpdate(editor: vscode.TextEditor, selection: vscode.Selection): vscode.Range[] {
const document = toLSTextDocument(editor.document);
const nodeToUpdate = getHtmlNodeLS(document, selection.start, true);
function getRangesToUpdate(document: vscode.TextDocument, selection: vscode.Selection, rootNode: HtmlFlatNode): vscode.Range[] {
const documentText = document.getText();
const offset = document.offsetAt(selection.start);
const nodeToUpdate = getHtmlFlatNode(documentText, rootNode, offset, true);
if (!nodeToUpdate) {
return [];
}

View file

@ -6,14 +6,13 @@
import * as vscode from 'vscode';
import parse from '@emmetio/html-matcher';
import parseStylesheet from '@emmetio/css-parser';
import { Node, HtmlNode, CssToken, Property, Rule, Stylesheet } from 'EmmetNode';
import { Node as FlatNode, HtmlNode as HtmlFlatNode, Property as FlatProperty, Rule as FlatRule, CssToken as FlatCssToken, Stylesheet as FlatStylesheet } from 'EmmetFlatNode';
import { DocumentStreamReader } from './bufferStream';
import * as EmmetHelper from 'vscode-emmet-helper';
import { Position as LSPosition, getLanguageService as getLanguageServiceInternal, LanguageService, LanguageServiceOptions, TextDocument as LSTextDocument, Node as LSNode } from 'vscode-html-languageservice';
import { parseMarkupDocument } from './parseMarkupDocument';
import { TextDocument as LSTextDocument } from 'vscode-languageserver-textdocument';
import { getRootNode } from './parseDocument';
let _emmetHelper: typeof EmmetHelper;
let _languageService: LanguageService;
let _currentExtensionsPath: string | undefined = undefined;
let _homeDir: vscode.Uri | undefined;
@ -33,16 +32,6 @@ export function getEmmetHelper() {
return _emmetHelper;
}
export function getLanguageService(options?: LanguageServiceOptions): LanguageService {
if (!options) {
if (!_languageService) {
_languageService = getLanguageServiceInternal();
}
return _languageService;
}
return getLanguageServiceInternal(options);
}
/**
* Update Emmet Helper to use user snippets from the extensionsPath setting
*/
@ -148,21 +137,6 @@ export function getEmmetMode(language: string, excludedLanguages: string[]): str
return;
}
/**
* Parses the given document using emmet parsing modules
*/
export function parseDocument(document: vscode.TextDocument, showError: boolean = true): Node | undefined {
let parseContent = isStyleSheet(document.languageId) ? parseStylesheet : parse;
try {
return parseContent(new DocumentStreamReader(document));
} catch (e) {
if (showError) {
vscode.window.showErrorMessage('Emmet: Failed to parse the file');
}
}
return undefined;
}
const closeBrace = 125;
const openBrace = 123;
const slash = 47;
@ -174,39 +148,41 @@ const star = 42;
* @param document vscode.TextDocument
* @param position vscode.Position
*/
export function parsePartialStylesheet(document: vscode.TextDocument, position: vscode.Position): Stylesheet | undefined {
export function parsePartialStylesheet(document: vscode.TextDocument, position: vscode.Position): FlatStylesheet | undefined {
const isCSS = document.languageId === 'css';
let startPosition = new vscode.Position(0, 0);
let endPosition = new vscode.Position(document.lineCount - 1, document.lineAt(document.lineCount - 1).text.length);
const limitCharacter = document.offsetAt(position) - 5000;
const limitPosition = limitCharacter > 0 ? document.positionAt(limitCharacter) : startPosition;
const stream = new DocumentStreamReader(document, position);
const positionOffset = document.offsetAt(position);
let startOffset = 0;
let endOffset = document.getText().length;
const limitCharacter = positionOffset - 5000;
const limitOffset = limitCharacter > 0 ? limitCharacter : startOffset;
const stream = new DocumentStreamReader(document, positionOffset);
function findOpeningCommentBeforePosition(pos: vscode.Position): vscode.Position | undefined {
let text = document.getText(new vscode.Range(0, 0, pos.line, pos.character));
function findOpeningCommentBeforePosition(pos: number): number | undefined {
const text = document.getText().substring(0, pos);
let offset = text.lastIndexOf('/*');
if (offset === -1) {
return;
}
return document.positionAt(offset);
return offset;
}
function findClosingCommentAfterPosition(pos: vscode.Position): vscode.Position | undefined {
let text = document.getText(new vscode.Range(pos.line, pos.character, document.lineCount - 1, document.lineAt(document.lineCount - 1).text.length));
function findClosingCommentAfterPosition(pos: number): number | undefined {
const text = document.getText().substring(pos);
let offset = text.indexOf('*/');
if (offset === -1) {
return;
}
offset += 2 + document.offsetAt(pos);
return document.positionAt(offset);
offset += 2 + pos;
return offset;
}
function consumeLineCommentBackwards() {
if (!isCSS && currentLine !== stream.pos.line) {
currentLine = stream.pos.line;
let startLineComment = document.lineAt(currentLine).text.indexOf('//');
const posLineNumber = document.positionAt(stream.pos).line;
if (!isCSS && currentLine !== posLineNumber) {
currentLine = posLineNumber;
const startLineComment = document.lineAt(currentLine).text.indexOf('//');
if (startLineComment > -1) {
stream.pos = new vscode.Position(currentLine, startLineComment);
stream.pos = document.offsetAt(new vscode.Position(currentLine, startLineComment));
}
}
}
@ -214,7 +190,7 @@ export function parsePartialStylesheet(document: vscode.TextDocument, position:
function consumeBlockCommentBackwards() {
if (stream.peek() === slash) {
if (stream.backUp(1) === star) {
stream.pos = findOpeningCommentBeforePosition(stream.pos) || startPosition;
stream.pos = findOpeningCommentBeforePosition(stream.pos) ?? startOffset;
} else {
stream.next();
}
@ -224,9 +200,10 @@ export function parsePartialStylesheet(document: vscode.TextDocument, position:
function consumeCommentForwards() {
if (stream.eat(slash)) {
if (stream.eat(slash) && !isCSS) {
stream.pos = new vscode.Position(stream.pos.line + 1, 0);
const posLineNumber = document.positionAt(stream.pos).line;
stream.pos = document.offsetAt(new vscode.Position(posLineNumber + 1, 0));
} else if (stream.eat(star)) {
stream.pos = findClosingCommentAfterPosition(stream.pos) || endPosition;
stream.pos = findClosingCommentAfterPosition(stream.pos) ?? endOffset;
}
}
}
@ -241,10 +218,10 @@ export function parsePartialStylesheet(document: vscode.TextDocument, position:
}
if (!stream.eof()) {
endPosition = stream.pos;
endOffset = stream.pos;
}
stream.pos = position;
stream.pos = positionOffset;
let openBracesToFind = 1;
let currentLine = position.line;
let exit = false;
@ -260,7 +237,7 @@ export function parsePartialStylesheet(document: vscode.TextDocument, position:
case closeBrace:
if (isCSS) {
stream.next();
startPosition = stream.pos;
startOffset = stream.pos;
exit = true;
} else {
openBracesToFind++;
@ -273,17 +250,17 @@ export function parsePartialStylesheet(document: vscode.TextDocument, position:
break;
}
if (position.line - stream.pos.line > 100 || stream.pos.isBeforeOrEqual(limitPosition)) {
if (position.line - document.positionAt(stream.pos).line > 100
|| stream.pos <= limitOffset) {
exit = true;
}
}
// We are at an opening brace. We need to include its selector.
currentLine = stream.pos.line;
currentLine = document.positionAt(stream.pos).line;
openBracesToFind = 0;
let foundSelector = false;
while (!exit && !stream.sof() && !foundSelector && openBracesToFind >= 0) {
consumeLineCommentBackwards();
const ch = stream.backUp(1);
@ -309,12 +286,13 @@ export function parsePartialStylesheet(document: vscode.TextDocument, position:
}
if (!stream.sof() && foundSelector) {
startPosition = stream.pos;
startOffset = stream.pos;
}
}
try {
return parseStylesheet(new DocumentStreamReader(document, startPosition, new vscode.Range(startPosition, endPosition)));
const buffer = ' '.repeat(startOffset) + document.getText().substring(startOffset, endOffset);
return parseStylesheet(buffer);
} catch (e) {
return;
}
@ -323,94 +301,70 @@ export function parsePartialStylesheet(document: vscode.TextDocument, position:
/**
* Returns node corresponding to given position in the given root node
*/
export function getNode(root: Node | undefined, position: vscode.Position, includeNodeBoundary: boolean) {
export function getFlatNode(root: FlatNode | undefined, offset: number, includeNodeBoundary: boolean): FlatNode | undefined {
if (!root) {
return null;
return;
}
let currentNode = root.firstChild;
let foundNode: Node | null = null;
while (currentNode) {
const nodeStart: vscode.Position = currentNode.start;
const nodeEnd: vscode.Position = currentNode.end;
if ((nodeStart.isBefore(position) && nodeEnd.isAfter(position))
|| (includeNodeBoundary && (nodeStart.isBeforeOrEqual(position) && nodeEnd.isAfterOrEqual(position)))) {
foundNode = currentNode;
// Dig deeper
currentNode = currentNode.firstChild;
} else {
currentNode = currentNode.nextSibling;
function getFlatNodeChild(child: FlatNode | undefined): FlatNode | undefined {
if (!child) {
return;
}
const nodeStart = child.start;
const nodeEnd = child.end;
if ((nodeStart < offset && nodeEnd > offset)
|| (includeNodeBoundary && nodeStart <= offset && nodeEnd >= offset)) {
return getFlatNodeChildren(child.children) ?? child;
}
else if ('close' in <any>child) {
// We have an HTML node in this case.
// In case this node is an invalid unpaired HTML node,
// we still want to search its children
const htmlChild = <HtmlFlatNode>child;
if (htmlChild.open && !htmlChild.close) {
return getFlatNodeChildren(htmlChild.children);
}
}
return;
}
return foundNode;
function getFlatNodeChildren(children: FlatNode[]): FlatNode | undefined {
for (let i = 0; i < children.length; i++) {
const foundChild = getFlatNodeChild(children[i]);
if (foundChild) {
return foundChild;
}
}
return;
}
return getFlatNodeChildren(root.children);
}
export const allowedMimeTypesInScriptTag = ['text/html', 'text/plain', 'text/x-template', 'text/template', 'text/ng-template'];
/**
* Returns HTML node corresponding to given position in the given root node
* Finds the HTML node within an HTML document at a given position
* If position is inside a script tag of type template, then it will be parsed to find the inner HTML node as well
*/
export function getHtmlNode(document: vscode.TextDocument, root: Node | undefined, position: vscode.Position, includeNodeBoundary: boolean): HtmlNode | undefined {
let currentNode = <HtmlNode>getNode(root, position, includeNodeBoundary);
export function getHtmlFlatNode(documentText: string, root: FlatNode | undefined, offset: number, includeNodeBoundary: boolean): HtmlFlatNode | undefined {
const currentNode: HtmlFlatNode | undefined = <HtmlFlatNode | undefined>getFlatNode(root, offset, includeNodeBoundary);
if (!currentNode) { return; }
const isTemplateScript = currentNode.name === 'script' &&
(currentNode.attributes &&
currentNode.attributes.some(x => x.name.toString() === 'type'
&& allowedMimeTypesInScriptTag.indexOf(x.value.toString()) > -1));
if (isTemplateScript && currentNode.close &&
(position.isAfter(currentNode.open.end) && position.isBefore(currentNode.close.start))) {
let buffer = new DocumentStreamReader(document, currentNode.open.end, new vscode.Range(currentNode.open.end, currentNode.close.start));
try {
let scriptInnerNodes = parse(buffer);
currentNode = <HtmlNode>getNode(scriptInnerNodes, position, includeNodeBoundary) || currentNode;
} catch (e) { }
}
return currentNode;
}
/**
* Finds the HTML node within an HTML document at a given position
*/
export function getHtmlNodeLS(document: LSTextDocument, position: vscode.Position, includeNodeBoundary: boolean): LSNode | undefined {
const documentText = document.getText();
const offset = document.offsetAt(position);
let selectionStartOffset = offset;
if (includeNodeBoundary && documentText.charAt(offset) === '<') {
selectionStartOffset++;
}
else if (includeNodeBoundary && documentText.charAt(offset) === '>') {
selectionStartOffset--;
}
return getHtmlNodeLSInternal(document, selectionStartOffset);
}
function getHtmlNodeLSInternal(document: LSTextDocument, offset: number, isInTemplateNode: boolean = false): LSNode | undefined {
const useCache = !isInTemplateNode;
const parsedDocument = parseMarkupDocument(document, useCache);
const currentNode: LSNode = parsedDocument.findNodeAt(offset);
if (!currentNode.tag) { return; }
const isTemplateScript = isNodeTemplateScriptLS(currentNode);
&& allowedMimeTypesInScriptTag.includes(x.value.toString())));
if (isTemplateScript
&& currentNode.startTagEnd
&& offset > currentNode.startTagEnd
&& (!currentNode.endTagStart || offset < currentNode.endTagStart)) {
&& currentNode.open
&& offset > currentNode.open.end
&& (!currentNode.close || offset < currentNode.close.start)) {
// blank out the rest of the document and search for the node within
const documentText = document.getText();
const beforePadding = ' '.repeat(currentNode.startTagEnd);
const scriptBodyText = beforePadding + documentText.substring(currentNode.startTagEnd, currentNode.endTagStart ?? currentNode.end);
const scriptBodyDocument = LSTextDocument.create(document.uri, document.languageId, document.version, scriptBodyText);
const scriptBodyNode = getHtmlNodeLSInternal(scriptBodyDocument, offset, true);
const beforePadding = ' '.repeat(currentNode.open.end);
const endToUse = currentNode.close ? currentNode.close.start : currentNode.end;
const scriptBodyText = beforePadding + documentText.substring(currentNode.open.end, endToUse);
const innerRoot: HtmlFlatNode = parse(scriptBodyText);
const scriptBodyNode = getHtmlFlatNode(scriptBodyText, innerRoot, offset, includeNodeBoundary);
if (scriptBodyNode) {
scriptBodyNode.parent = currentNode;
currentNode.children.push(scriptBodyNode);
@ -420,55 +374,28 @@ function getHtmlNodeLSInternal(document: LSTextDocument, offset: number, isInTem
return currentNode;
}
/**
* Returns whether the node is a <script> node
* that we want to search through and parse for more potential HTML nodes
*/
function isNodeTemplateScriptLS(node: LSNode): boolean {
if (node.tag === 'script' && node.attributes && node.attributes['type']) {
let scriptType = node.attributes['type'];
scriptType = scriptType.substring(1, scriptType.length - 1);
return allowedMimeTypesInScriptTag.includes(scriptType);
}
return false;
}
function toVsPosition(position: LSPosition): vscode.Position {
return new vscode.Position(position.line, position.character);
}
export function offsetRangeToSelection(document: LSTextDocument, start: number, end: number): vscode.Selection {
export function offsetRangeToSelection(document: vscode.TextDocument, start: number, end: number): vscode.Selection {
const startPos = document.positionAt(start);
const endPos = document.positionAt(end);
return new vscode.Selection(toVsPosition(startPos), toVsPosition(endPos));
return new vscode.Selection(startPos, endPos);
}
export function offsetRangeToVsRange(document: LSTextDocument, start: number, end: number): vscode.Range {
export function offsetRangeToVsRange(document: vscode.TextDocument, start: number, end: number): vscode.Range {
const startPos = document.positionAt(start);
const endPos = document.positionAt(end);
return new vscode.Range(toVsPosition(startPos), toVsPosition(endPos));
}
/**
* Returns inner range of an html node.
*/
export function getInnerRange(currentNode: HtmlNode): vscode.Range | undefined {
if (!currentNode.close) {
return undefined;
}
return new vscode.Range(currentNode.open.end, currentNode.close.start);
return new vscode.Range(startPos, endPos);
}
/**
* Returns the deepest non comment node under given node
*/
export function getDeepestNode(node: Node | undefined): Node | undefined {
export function getDeepestFlatNode(node: FlatNode | undefined): FlatNode | undefined {
if (!node || !node.children || node.children.length === 0 || !node.children.find(x => x.type !== 'comment')) {
return node;
}
for (let i = node.children.length - 1; i >= 0; i--) {
if (node.children[i].type !== 'comment') {
return getDeepestNode(node.children[i]);
return getDeepestFlatNode(node.children[i]);
}
}
return undefined;
@ -550,7 +477,7 @@ export function findPrevWord(propertyValue: string, pos: number): [number | unde
return [newSelectionStart, newSelectionEnd];
}
export function getNodesInBetween(node1: Node, node2: Node): Node[] {
export function getNodesInBetween(node1: FlatNode, node2: FlatNode): FlatNode[] {
// Same node
if (sameNodes(node1, node2)) {
return [node1];
@ -559,41 +486,46 @@ export function getNodesInBetween(node1: Node, node2: Node): Node[] {
// Not siblings
if (!sameNodes(node1.parent, node2.parent)) {
// node2 is ancestor of node1
if (node2.start.isBefore(node1.start)) {
if (node2.start < node1.start) {
return [node2];
}
// node1 is ancestor of node2
if (node2.start.isBefore(node1.end)) {
if (node2.start < node1.end) {
return [node1];
}
// Get the highest ancestor of node1 that should be commented
while (node1.parent && node1.parent.end.isBefore(node2.start)) {
while (node1.parent && node1.parent.end < node2.start) {
node1 = node1.parent;
}
// Get the highest ancestor of node2 that should be commented
while (node2.parent && node2.parent.start.isAfter(node1.start)) {
while (node2.parent && node2.parent.start > node1.start) {
node2 = node2.parent;
}
}
const siblings: Node[] = [];
let currentNode = node1;
const siblings: FlatNode[] = [];
let currentNode: FlatNode | undefined = node1;
const position = node2.end;
while (currentNode && position.isAfter(currentNode.start)) {
while (currentNode && position > currentNode.start) {
siblings.push(currentNode);
currentNode = currentNode.nextSibling;
}
return siblings;
}
export function sameNodes(node1: Node, node2: Node): boolean {
export function sameNodes(node1: FlatNode | undefined, node2: FlatNode | undefined): boolean {
// return true if they're both undefined
if (!node1 && !node2) {
return true;
}
// return false if only one of them is undefined
if (!node1 || !node2) {
return false;
}
return (<vscode.Position>node1.start).isEqual(node2.start) && (<vscode.Position>node1.end).isEqual(node2.end);
return node1.start === node2.start && node1.end === node2.end;
}
export function getEmmetConfiguration(syntax: string) {
@ -629,7 +561,7 @@ export function getEmmetConfiguration(syntax: string) {
* Itereates by each child, as well as nested child's children, in their order
* and invokes `fn` for each. If `fn` function returns `false`, iteration stops
*/
export function iterateCSSToken(token: CssToken, fn: (x: any) => any): boolean {
export function iterateCSSToken(token: FlatCssToken, fn: (x: any) => any): boolean {
for (let i = 0, il = token.size; i < il; i++) {
if (fn(token.item(i)) === false || iterateCSSToken(token.item(i), fn) === false) {
return false;
@ -641,51 +573,53 @@ export function iterateCSSToken(token: CssToken, fn: (x: any) => any): boolean {
/**
* Returns `name` CSS property from given `rule`
*/
export function getCssPropertyFromRule(rule: Rule, name: string): Property | undefined {
return rule.children.find(node => node.type === 'property' && node.name === name) as Property;
export function getCssPropertyFromRule(rule: FlatRule, name: string): FlatProperty | undefined {
return rule.children.find(node => node.type === 'property' && node.name === name) as FlatProperty;
}
/**
* Returns css property under caret in given editor or `null` if such node cannot
* be found
*/
export function getCssPropertyFromDocument(editor: vscode.TextEditor, position: vscode.Position): Property | null {
const rootNode = parseDocument(editor.document);
const node = getNode(rootNode, position, true);
export function getCssPropertyFromDocument(editor: vscode.TextEditor, position: vscode.Position): FlatProperty | null {
const document = editor.document;
const rootNode = getRootNode(document, true);
const offset = document.offsetAt(position);
const node = getFlatNode(rootNode, offset, true);
if (isStyleSheet(editor.document.languageId)) {
return node && node.type === 'property' ? <Property>node : null;
return node && node.type === 'property' ? <FlatProperty>node : null;
}
let htmlNode = <HtmlNode>node;
const htmlNode = <HtmlFlatNode>node;
if (htmlNode
&& htmlNode.name === 'style'
&& htmlNode.open.end.isBefore(position)
&& htmlNode.close.start.isAfter(position)) {
let buffer = new DocumentStreamReader(editor.document, htmlNode.start, new vscode.Range(htmlNode.start, htmlNode.end));
let rootNode = parseStylesheet(buffer);
const node = getNode(rootNode, position, true);
return (node && node.type === 'property') ? <Property>node : null;
&& htmlNode.open && htmlNode.close
&& htmlNode.open.end < offset
&& htmlNode.close.start > offset) {
const buffer = ' '.repeat(htmlNode.start) +
document.getText().substring(htmlNode.start, htmlNode.end);
const innerRootNode = parseStylesheet(buffer);
const innerNode = getFlatNode(innerRootNode, offset, true);
return (innerNode && innerNode.type === 'property') ? <FlatProperty>innerNode : null;
}
return null;
}
export function getEmbeddedCssNodeIfAny(document: vscode.TextDocument, currentNode: Node | null, position: vscode.Position): Node | undefined {
export function getEmbeddedCssNodeIfAny(document: vscode.TextDocument, currentNode: FlatNode | undefined, position: vscode.Position): FlatNode | undefined {
if (!currentNode) {
return;
}
const currentHtmlNode = <HtmlNode>currentNode;
if (currentHtmlNode && currentHtmlNode.close) {
const innerRange = getInnerRange(currentHtmlNode);
if (innerRange && innerRange.contains(position)) {
const currentHtmlNode = <HtmlFlatNode>currentNode;
if (currentHtmlNode && currentHtmlNode.open && currentHtmlNode.close) {
const offset = document.offsetAt(position);
if (currentHtmlNode.open.end <= offset && offset <= currentHtmlNode.close.start) {
if (currentHtmlNode.name === 'style'
&& currentHtmlNode.open.end.isBefore(position)
&& currentHtmlNode.close.start.isAfter(position)
) {
let buffer = new DocumentStreamReader(document, currentHtmlNode.open.end, new vscode.Range(currentHtmlNode.open.end, currentHtmlNode.close.start));
&& currentHtmlNode.open.end < offset
&& currentHtmlNode.close.start > offset) {
const buffer = ' '.repeat(currentHtmlNode.open.end) + document.getText().substring(currentHtmlNode.open.end, currentHtmlNode.close.start);
return parseStylesheet(buffer);
}
}
@ -693,34 +627,17 @@ export function getEmbeddedCssNodeIfAny(document: vscode.TextDocument, currentNo
return;
}
export function isStyleAttribute(currentNode: Node | null, position: vscode.Position): boolean {
export function isStyleAttribute(currentNode: FlatNode | undefined, offset: number): boolean {
if (!currentNode) {
return false;
}
const currentHtmlNode = <HtmlNode>currentNode;
const currentHtmlNode = <HtmlFlatNode>currentNode;
const index = (currentHtmlNode.attributes || []).findIndex(x => x.name.toString() === 'style');
if (index === -1) {
return false;
}
const styleAttribute = currentHtmlNode.attributes[index];
return position.isAfterOrEqual(styleAttribute.value.start) && position.isBeforeOrEqual(styleAttribute.value.end);
}
export function trimQuotes(s: string) {
if (s.length <= 1) {
return s.replace(/['"]/, '');
}
if (s[0] === `'` || s[0] === `"`) {
s = s.slice(1);
}
if (s[s.length - 1] === `'` || s[s.length - 1] === `"`) {
s = s.slice(0, -1);
}
return s;
return offset >= styleAttribute.value.start && offset <= styleAttribute.value.end;
}
export function isNumber(obj: any): obj is number {

View file

@ -2,10 +2,10 @@
# yarn lockfile v1
"@emmetio/abbreviation@^2.0.2":
version "2.0.2"
resolved "https://registry.yarnpkg.com/@emmetio/abbreviation/-/abbreviation-2.0.2.tgz#e26d55d78c00cdeb2ef983e902c7ad55ed0b648d"
integrity sha512-kpWg6jyR1YEj/yWceruvDj/fe1BhXqA0tGH3Z2ZiPFo8SDMH4JHg6FChqon5x0CCfLf4zVswrQa0gcZ4XtdRBQ==
"@emmetio/abbreviation@^2.2.0":
version "2.2.0"
resolved "https://registry.yarnpkg.com/@emmetio/abbreviation/-/abbreviation-2.2.0.tgz#9f8dedbdb00e3136d6d37c6415375c82c0bb477f"
integrity sha512-NPGVUmnr7cLj4i6MKS4c8NjuoIIJROrruJl/8nXsp2MdbDRHvtfq25foySvv/NbfqTQm+P9JzVLDD9JxGIpvkQ==
dependencies:
"@emmetio/scanner" "^1.0.0"
@ -54,16 +54,15 @@
integrity sha1-Rs/+oRmgoAMxKiHC2bVijLX81EI=
"@types/node@^12.19.9":
version "12.19.9"
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.19.9.tgz#990ad687ad8b26ef6dcc34a4f69c33d40c95b679"
integrity sha512-yj0DOaQeUrk3nJ0bd3Y5PeDRJ6W0r+kilosLA+dzF3dola/o9hxhMSg2sFvVcA2UHS5JSOsZp4S0c1OEXc4m1Q==
version "12.19.12"
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.19.12.tgz#04793c2afa4ce833a9972e4c476432e30f9df47b"
integrity sha512-UwfL2uIU9arX/+/PRcIkT08/iBadGN2z6ExOROA2Dh5mAuWTBj6iJbQX4nekiV5H8cTrEG569LeX+HRco9Cbxw==
emmet@^2.1.5:
version "2.1.6"
resolved "https://registry.yarnpkg.com/emmet/-/emmet-2.1.6.tgz#425e0bcef6bf6e5eb758610f3e8d49f86a6fe877"
integrity sha512-kfJMlze+k8jpX5CUx7xPYS83DxRNXuh8rQ98rQKnnf+wfo/KD+BG6pmpnEp5a7a1DWM9xmllKuOPfC7MeRmepQ==
"emmet@https://github.com/rzhao271/emmet.git#1b2df677d8925ef5ea6da9df8845968403979a0a":
version "2.3.0"
resolved "https://github.com/rzhao271/emmet.git#1b2df677d8925ef5ea6da9df8845968403979a0a"
dependencies:
"@emmetio/abbreviation" "^2.0.2"
"@emmetio/abbreviation" "^2.2.0"
"@emmetio/css-abbreviation" "^2.1.2"
image-size@^0.5.2:
@ -76,42 +75,27 @@ jsonc-parser@^2.3.0:
resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.3.1.tgz#59549150b133f2efacca48fe9ce1ec0659af2342"
integrity sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg==
vscode-emmet-helper@~2.0.0:
version "2.0.9"
resolved "https://registry.yarnpkg.com/vscode-emmet-helper/-/vscode-emmet-helper-2.0.9.tgz#16244c087cba4e379116f268384bb644649db6ad"
integrity sha512-S6RjnR9gUicl8LsYnQAMNqqOxolud9gcj+NpPyEnxfxp1YIBuC9oetj6l6N9VMZBWu6tL77wmf+/EJsRx1PDPA==
vscode-emmet-helper@2.2.4-2:
version "2.2.4-2"
resolved "https://registry.yarnpkg.com/vscode-emmet-helper/-/vscode-emmet-helper-2.2.4-2.tgz#8019188077a91dbe9a8d8c10c0b79369bb5c24d6"
integrity sha512-7UTZXwt9M1xwaV72o2YgSBVoghtDtscTgYTOl1kiPkXN9OKiM4N52hcHFA1LlRtdTvIQd4PEkgaz57F9ZT/4kg==
dependencies:
emmet "^2.1.5"
emmet "https://github.com/rzhao271/emmet.git#1b2df677d8925ef5ea6da9df8845968403979a0a"
jsonc-parser "^2.3.0"
vscode-languageserver-textdocument "^1.0.1"
vscode-languageserver-types "^3.15.1"
vscode-nls "^5.0.0"
vscode-uri "^2.1.2"
vscode-html-languageservice@^3.0.3:
version "3.1.4"
resolved "https://registry.yarnpkg.com/vscode-html-languageservice/-/vscode-html-languageservice-3.1.4.tgz#0316dff77ee38dc176f40560cbf55e4f64f4f433"
integrity sha512-3M+bm+hNvwQcScVe5/ok9BXvctOiGJ4nlOkkFf+WKSDrYNkarZ/RByKOa1/iylbvZxJUPzbeziembWPe/dMvhw==
dependencies:
vscode-languageserver-textdocument "^1.0.1"
vscode-languageserver-types "3.16.0-next.2"
vscode-nls "^5.0.0"
vscode-uri "^2.1.2"
vscode-languageserver-textdocument@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.1.tgz#178168e87efad6171b372add1dea34f53e5d330f"
integrity sha512-UIcJDjX7IFkck7cSkNNyzIz5FyvpQfY7sdzVy+wkKN/BLaD4DQ0ppXQrKePomCxTS7RrolK1I0pey0bG9eh8dA==
vscode-languageserver-types@3.16.0-next.2:
version "3.16.0-next.2"
resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0-next.2.tgz#940bd15c992295a65eae8ab6b8568a1e8daa3083"
integrity sha512-QjXB7CKIfFzKbiCJC4OWC8xUncLsxo19FzGVp/ADFvvi87PlmBSCAtZI5xwGjF5qE0xkLf0jjKUn3DzmpDP52Q==
vscode-languageserver-types@^3.15.1:
version "3.15.1"
resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.15.1.tgz#17be71d78d2f6236d414f0001ce1ef4d23e6b6de"
integrity sha512-+a9MPUQrNGRrGU630OGbYVQ+11iOIovjCkqxajPa9w57Sd5ruK8WQNsslzpa0x/QJqC8kRc2DUxWjIFwoNm4ZQ==
version "3.16.0"
resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz#ecf393fc121ec6974b2da3efb3155644c514e247"
integrity sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA==
vscode-nls@^5.0.0:
version "5.0.0"

View file

@ -432,7 +432,11 @@ export class Git {
if (options.recursive) {
command.push('--recursive');
}
await this.exec(options.parentPath, command, { cancellationToken, onSpawn });
await this.exec(options.parentPath, command, {
cancellationToken,
env: { 'GIT_HTTP_USER_AGENT': this.userAgent },
onSpawn,
});
} catch (err) {
if (err.stderr) {
err.stderr = err.stderr.replace(/^Cloning.+$/m, '').trim();
@ -1568,6 +1572,7 @@ export class Repository {
const args = ['fetch'];
const spawnOptions: SpawnOptions = {
cancellationToken: options.cancellationToken,
env: { 'GIT_HTTP_USER_AGENT': this.git.userAgent }
};
if (options.remote) {
@ -1589,7 +1594,7 @@ export class Repository {
}
if (options.silent) {
spawnOptions.env = { 'VSCODE_GIT_FETCH_SILENT': 'true' };
spawnOptions.env!['VSCODE_GIT_FETCH_SILENT'] = 'true';
}
try {
@ -1626,7 +1631,10 @@ export class Repository {
}
try {
await this.run(args, options);
await this.run(args, {
cancellationToken: options.cancellationToken,
env: { 'GIT_HTTP_USER_AGENT': this.git.userAgent }
});
} catch (err) {
if (/^CONFLICT \([^)]+\): \b/m.test(err.stdout || '')) {
err.gitErrorCode = GitErrorCodes.Conflict;
@ -1694,10 +1702,8 @@ export class Repository {
args.push(name);
}
args.splice(0, 0, '-c', `http.userAgent=${this.git.userAgent}`);
try {
await this.run(args);
await this.run(args, { env: { 'GIT_HTTP_USER_AGENT': this.git.userAgent } });
} catch (err) {
if (/^error: failed to push some refs to\b/m.test(err.stderr || '')) {
err.gitErrorCode = GitErrorCodes.PushRejected;
@ -1909,11 +1915,11 @@ export class Repository {
const fn = (line: string): Ref | null => {
let match: RegExpExecArray | null;
if (match = /^refs\/heads\/([^ ]+) ([0-9a-f]{40}) ([0-9a-f]{40})?$/.exec(line)) {
if (match = /^refs\/heads\/([^ ]+) ([0-9a-f]{40}) ?([0-9a-f]{40})?$/.exec(line)) {
return { name: match[1], commit: match[2], type: RefType.Head };
} else if (match = /^refs\/remotes\/([^/]+)\/([^ ]+) ([0-9a-f]{40}) ([0-9a-f]{40})?$/.exec(line)) {
} else if (match = /^refs\/remotes\/([^/]+)\/([^ ]+) ?([0-9a-f]{40}) ([0-9a-f]{40})?$/.exec(line)) {
return { name: `${match[1]}/${match[2]}`, commit: match[3], type: RefType.RemoteHead, remote: match[1] };
} else if (match = /^refs\/tags\/([^ ]+) ([0-9a-f]{40}) ([0-9a-f]{40})?$/.exec(line)) {
} else if (match = /^refs\/tags\/([^ ]+) ([0-9a-f]{40}) ?([0-9a-f]{40})?$/.exec(line)) {
return { name: match[1], commit: match[3] ?? match[2], type: RefType.Tag };
}

View file

@ -79,7 +79,7 @@ export class Resource implements SourceControlResourceState {
return this.resources[0];
}
get rightUri(): Uri {
get rightUri(): Uri | undefined {
return this.resources[1];
}
@ -88,7 +88,7 @@ export class Resource implements SourceControlResourceState {
}
@memoize
private get resources(): [Uri | undefined, Uri] {
private get resources(): [Uri | undefined, Uri | undefined] {
return this._commandResolver.getResources(this);
}
@ -613,7 +613,7 @@ class ResourceCommandResolver {
}
}
getResources(resource: Resource): [Uri | undefined, Uri] {
getResources(resource: Resource): [Uri | undefined, Uri | undefined] {
for (const submodule of this.repository.submodules) {
if (path.join(this.repository.root, submodule.path) === resource.resourceUri.fsPath) {
return [undefined, toGitUri(resource.resourceUri, resource.resourceGroupType === ResourceGroupType.Index ? 'index' : 'wt', { submoduleOf: this.repository.root })];
@ -641,7 +641,7 @@ class ResourceCommandResolver {
return undefined;
}
private getRightResource(resource: Resource): Uri {
private getRightResource(resource: Resource): Uri | undefined {
switch (resource.type) {
case Status.INDEX_MODIFIED:
case Status.INDEX_ADDED:
@ -677,7 +677,7 @@ class ResourceCommandResolver {
return resource.resourceUri;
}
throw new Error('Should never happen');
return undefined;
}
private getTitle(resource: Resource): string {

View file

@ -124,4 +124,31 @@ suite('git smoke test', function () {
assert.equal(repository.state.workingTreeChanges.length, 0);
assert.equal(repository.state.indexChanges.length, 0);
});
test('rename/delete conflict', async function () {
cp.execSync('git branch test', { cwd });
cp.execSync('git checkout test', { cwd });
fs.unlinkSync(file('app.js'));
cp.execSync('git add .', { cwd });
await repository.commit('commit on test');
cp.execSync('git checkout master', { cwd });
fs.renameSync(file('app.js'), file('rename.js'));
cp.execSync('git add .', { cwd });
await repository.commit('commit on master');
try {
cp.execSync('git merge test', { cwd });
} catch (e) { }
setTimeout(() => {
commands.executeCommand('workbench.scm.focus');
}, 2e3);
await new Promise(resolve => {
setTimeout(resolve, 5e3);
});
});
});

View file

@ -31,9 +31,10 @@ export type Keytar = {
const SERVICE_ID = `github.auth`;
export class Keychain {
constructor(private context: vscode.ExtensionContext) { }
async setToken(token: string): Promise<void> {
try {
return await vscode.authentication.setPassword(SERVICE_ID, token);
return await this.context.secrets.set(SERVICE_ID, token);
} catch (e) {
// Ignore
Logger.error(`Setting token failed: ${e}`);
@ -47,7 +48,7 @@ export class Keychain {
async getToken(): Promise<string | null | undefined> {
try {
return await vscode.authentication.getPassword(SERVICE_ID);
return await this.context.secrets.get(SERVICE_ID);
} catch (e) {
// Ignore
Logger.error(`Getting token failed: ${e}`);
@ -57,7 +58,7 @@ export class Keychain {
async deleteToken(): Promise<void> {
try {
return await vscode.authentication.deletePassword(SERVICE_ID);
return await this.context.secrets.delete(SERVICE_ID);
} catch (e) {
// Ignore
Logger.error(`Deleting token failed: ${e}`);
@ -85,5 +86,3 @@ export class Keychain {
}
}
}
export const keychain = new Keychain();

View file

@ -14,7 +14,7 @@ export async function activate(context: vscode.ExtensionContext) {
const telemetryReporter = new TelemetryReporter(name, version, aiKey);
context.subscriptions.push(vscode.window.registerUriHandler(uriHandler));
const loginService = new GitHubAuthenticationProvider();
const loginService = new GitHubAuthenticationProvider(context);
await loginService.initialize(context);

View file

@ -5,7 +5,7 @@
import * as vscode from 'vscode';
import { v4 as uuid } from 'uuid';
import { keychain } from './common/keychain';
import { Keychain } from './common/keychain';
import { GitHubServer, NETWORK_ERROR } from './githubServer';
import Logger from './common/logger';
@ -26,6 +26,12 @@ export class GitHubAuthenticationProvider {
private _sessions: vscode.AuthenticationSession[] = [];
private _githubServer = new GitHubServer();
private _keychain: Keychain;
constructor(context: vscode.ExtensionContext) {
this._keychain = new Keychain(context);
}
public async initialize(context: vscode.ExtensionContext): Promise<void> {
try {
this._sessions = await this.readSessions();
@ -34,7 +40,7 @@ export class GitHubAuthenticationProvider {
// Ignore, network request failed
}
context.subscriptions.push(vscode.authentication.onDidChangePassword(() => this.checkForUpdates()));
context.subscriptions.push(context.secrets.onDidChange(() => this.checkForUpdates()));
}
private async verifySessions(): Promise<void> {
@ -101,7 +107,7 @@ export class GitHubAuthenticationProvider {
}
private async readSessions(): Promise<vscode.AuthenticationSession[]> {
const storedSessions = await keychain.getToken() || await keychain.tryMigrate();
const storedSessions = await this._keychain.getToken() || await this._keychain.tryMigrate();
if (storedSessions) {
try {
const sessionData: SessionData[] = JSON.parse(storedSessions);
@ -132,7 +138,7 @@ export class GitHubAuthenticationProvider {
}
Logger.error(`Error reading sessions: ${e}`);
await keychain.deleteToken();
await this._keychain.deleteToken();
}
}
@ -140,7 +146,7 @@ export class GitHubAuthenticationProvider {
}
private async storeSessions(): Promise<void> {
await keychain.setToken(JSON.stringify(this._sessions));
await this._keychain.setToken(JSON.stringify(this._sessions));
}
get sessions(): vscode.AuthenticationSession[] {

View file

@ -13,12 +13,31 @@ const localize = nls.loadMessageBundle();
type AutoDetect = 'on' | 'off';
function exists(file: string): Promise<boolean> {
return new Promise<boolean>((resolve, _reject) => {
fs.exists(file, (value) => {
resolve(value);
});
});
/**
* Check if the given filename is a file.
*
* If returns false in case the file does not exist or
* the file stats cannot be accessed/queried or it
* is no file at all.
*
* @param filename
* the filename to the checked
* @returns
* true in case the file exists, in any other case false.
*/
async function exists(filename: string): Promise<boolean> {
try {
if ((await fs.promises.stat(filename)).isFile()) {
return true;
}
} catch (ex) {
// In case requesting the file statistics fail.
// we assume it does not exist.
return false;
}
return false;
}
function exec(command: string, options: cp.ExecOptions): Promise<{ stdout: string; stderr: string }> {
@ -70,21 +89,23 @@ function showError() {
}
async function findGulpCommand(rootPath: string): Promise<string> {
let gulpCommand: string;
let platform = process.platform;
if (platform === 'win32' && await exists(path.join(rootPath, 'node_modules', '.bin', 'gulp.cmd'))) {
const globalGulp = path.join(process.env.APPDATA ? process.env.APPDATA : '', 'npm', 'gulp.cmd');
if (await exists(globalGulp)) {
gulpCommand = '"' + globalGulp + '"';
} else {
gulpCommand = path.join('.', 'node_modules', '.bin', 'gulp.cmd');
return `"${globalGulp}"`;
}
} else if ((platform === 'linux' || platform === 'darwin') && await exists(path.join(rootPath, 'node_modules', '.bin', 'gulp'))) {
gulpCommand = path.join('.', 'node_modules', '.bin', 'gulp');
} else {
gulpCommand = 'gulp';
return path.join('.', 'node_modules', '.bin', 'gulp.cmd');
}
return gulpCommand;
if ((platform === 'linux' || platform === 'darwin') && await exists(path.join(rootPath, 'node_modules', '.bin', 'gulp'))) {
return path.join('.', 'node_modules', '.bin', 'gulp');
}
return 'gulp';
}
interface GulpTaskDefinition extends vscode.TaskDefinition {
@ -111,7 +132,7 @@ class FolderDetector {
}
public start(): void {
let pattern = path.join(this._workspaceFolder.uri.fsPath, '{node_modules,gulpfile{.babel.js,.js,.ts}}');
let pattern = path.join(this._workspaceFolder.uri.fsPath, '{node_modules,gulpfile{.babel.js,.esm.js,.js,.mjs,.cjs,.ts}}');
this.fileWatcher = vscode.workspace.createFileSystemWatcher(pattern);
this.fileWatcher.onDidChange(() => this.promise = undefined);
this.fileWatcher.onDidCreate(() => this.promise = undefined);
@ -119,14 +140,15 @@ class FolderDetector {
}
public async getTasks(): Promise<vscode.Task[]> {
if (this.isEnabled()) {
if (!this.promise) {
this.promise = this.computeTasks();
}
return this.promise;
} else {
if (!this.isEnabled()) {
return [];
}
if (!this.promise) {
this.promise = this.computeTasks();
}
return this.promise;
}
public async getTask(_task: vscode.Task): Promise<vscode.Task | undefined> {
@ -140,21 +162,55 @@ class FolderDetector {
return undefined;
}
/**
* Searches for a gulp entry point inside the given folder.
*
* Typically the entry point is a file named "gulpfile.js"
*
* It can also be a transposed gulp entry points, like gulp.babel.js or gulp.esm.js
*
* Additionally recent node version prefer the .mjs or .cjs extension over the .js.
*
* @param root
* the folder which should be checked.
*/
private async hasGulpfile(root: string): Promise<boolean | undefined> {
for (const filename of await fs.promises.readdir(root)) {
const ext = path.extname(filename);
if (ext !== '.js' && ext !== '.mjs' && ext !== '.cjs') {
continue;
}
if (!exists(filename)) {
continue;
}
let basename = path.basename(filename, ext).toLowerCase();
if (basename === 'gulpfile') {
return true;
}
if (basename === 'gulpfile.esm') {
return true;
}
if (basename === 'gulpfile.babel') {
return true;
}
}
return false;
}
private async computeTasks(): Promise<vscode.Task[]> {
let rootPath = this._workspaceFolder.uri.scheme === 'file' ? this._workspaceFolder.uri.fsPath : undefined;
let emptyTasks: vscode.Task[] = [];
if (!rootPath) {
return emptyTasks;
}
let gulpfile = path.join(rootPath, 'gulpfile.js');
if (!await exists(gulpfile)) {
gulpfile = path.join(rootPath, 'Gulpfile.js');
if (!await exists(gulpfile)) {
gulpfile = path.join(rootPath, 'gulpfile.babel.js');
if (!await exists(gulpfile)) {
return emptyTasks;
}
}
if (!this.hasGulpfile(rootPath)) {
return emptyTasks;
}
let commandLine = `${await this._gulpCommand} --tasks-simple --no-color`;

View file

@ -40,7 +40,7 @@ namespace SemanticTokenLegendRequest {
}
namespace SettingIds {
export const linkedRename = 'editor.linkedRename';
export const linkedEditing = 'editor.linkedEditing';
export const formatEnable = 'html.format.enable';
}
@ -284,25 +284,25 @@ export function startClient(context: ExtensionContext, newLanguageClient: Langua
}
});
const promptForTypeOnRenameKey = 'html.promptForTypeOnRename';
const promptForTypeOnRename = extensions.getExtension('formulahendry.auto-rename-tag') !== undefined &&
(context.globalState.get(promptForTypeOnRenameKey) !== false) &&
!workspace.getConfiguration('editor', { languageId: 'html' }).get('linkedRename');
if (promptForTypeOnRename) {
const activeEditorListener = window.onDidChangeActiveTextEditor(async e => {
if (e && documentSelector.indexOf(e.document.languageId) !== -1) {
context.globalState.update(promptForTypeOnRenameKey, false);
activeEditorListener.dispose();
const configure = localize('configureButton', 'Configure');
const res = await window.showInformationMessage(localize('linkedRenameQuestion', 'VS Code now has built-in support for auto-renaming tags. Do you want to enable it?'), configure);
if (res === configure) {
commands.executeCommand('workbench.action.openSettings', SettingIds.linkedRename);
const promptForLinkedEditingKey = 'html.promptForLinkedEditing';
if (extensions.getExtension('formulahendry.auto-rename-tag') !== undefined && (context.globalState.get(promptForLinkedEditingKey) !== false)) {
const config = workspace.getConfiguration('editor', { languageId: 'html' });
if (!config.get('linkedEditing') && !config.get('renameOnType')) {
const activeEditorListener = window.onDidChangeActiveTextEditor(async e => {
if (e && documentSelector.indexOf(e.document.languageId) !== -1) {
context.globalState.update(promptForLinkedEditingKey, false);
activeEditorListener.dispose();
const configure = localize('configureButton', 'Configure');
const res = await window.showInformationMessage(localize('linkedEditingQuestion', 'VS Code now has built-in support for auto-renaming tags. Do you want to enable it?'), configure);
if (res === configure) {
commands.executeCommand('workbench.action.openSettings', SettingIds.linkedEditing);
}
}
}
});
toDispose.push(activeEditorListener);
});
toDispose.push(activeEditorListener);
}
}
toDispose.push();
}

View file

@ -20,7 +20,6 @@
"scripts": {
"compile": "npx gulp compile-extension:html-language-features-client compile-extension:html-language-features-server",
"watch": "npx gulp watch-extension:html-language-features-client watch-extension:html-language-features-server",
"postinstall": "cd server && yarn install",
"install-client-next": "yarn add vscode-languageclient@next"
},
"categories": [
@ -148,17 +147,13 @@
"description": "%html.format.wrapAttributesIndentSize.desc%"
},
"html.format.templating": {
"type": [
"boolean"
],
"type": "boolean",
"scope": "resource",
"default": false,
"description": "%html.format.templating.desc%"
},
"html.format.unformattedContentDelimiter": {
"type": [
"string"
],
"type": "string",
"scope": "resource",
"default": "",
"markdownDescription": "%html.format.unformattedContentDelimiter.desc%"

View file

@ -4,7 +4,7 @@
"If you want to provide a fix or improvement, please create a pull request against the original repository.",
"Once accepted there, we are happy to receive an update request."
],
"version": "https://github.com/microsoft/TypeScript-TmLanguage/commit/a755c6bad619daa8349fb23149eeeff220a06603",
"version": "https://github.com/microsoft/TypeScript-TmLanguage/commit/398985941eb36cd270054a6e668d03fb9ef92e77",
"name": "JavaScript (with React support)",
"scopeName": "source.js",
"patterns": [
@ -2987,13 +2987,7 @@
"end": "(?<=\\))|(?=[;),}\\]:?\\-\\+\\>]|\\|\\||\\&\\&|\\!\\=\\=|$|((?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))new(?![_$[:alnum:]])(?:(?=\\.\\.\\.)|(?!\\.)))|((?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))function((\\s+[_$[:alpha:]][_$[:alnum:]]*)|(\\s*[\\(]))))",
"patterns": [
{
"include": "#paren-expression"
},
{
"include": "#class-declaration"
},
{
"include": "#type"
"include": "#expression"
}
]
},
@ -3359,7 +3353,7 @@
"name": "keyword.operator.expression.typeof.js"
}
},
"end": "(?=[,);}\\]=]|$|;|(?:^\\s*(?:abstract|async|class|const|declare|enum|export|function|import|interface|let|module|namespace|return|type|var)\\b))",
"end": "(?=[,);}\\]=>]|$|;|(?:^\\s*(?:abstract|async|class|const|declare|enum|export|function|import|interface|let|module|namespace|return|type|var)\\b))",
"patterns": [
{
"include": "#expression"

View file

@ -4,7 +4,7 @@
"If you want to provide a fix or improvement, please create a pull request against the original repository.",
"Once accepted there, we are happy to receive an update request."
],
"version": "https://github.com/microsoft/TypeScript-TmLanguage/commit/a755c6bad619daa8349fb23149eeeff220a06603",
"version": "https://github.com/microsoft/TypeScript-TmLanguage/commit/398985941eb36cd270054a6e668d03fb9ef92e77",
"name": "JavaScript (with React support)",
"scopeName": "source.js.jsx",
"patterns": [
@ -2987,13 +2987,7 @@
"end": "(?<=\\))|(?=[;),}\\]:?\\-\\+\\>]|\\|\\||\\&\\&|\\!\\=\\=|$|((?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))new(?![_$[:alnum:]])(?:(?=\\.\\.\\.)|(?!\\.)))|((?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))function((\\s+[_$[:alpha:]][_$[:alnum:]]*)|(\\s*[\\(]))))",
"patterns": [
{
"include": "#paren-expression"
},
{
"include": "#class-declaration"
},
{
"include": "#type"
"include": "#expression"
}
]
},
@ -3359,7 +3353,7 @@
"name": "keyword.operator.expression.typeof.js.jsx"
}
},
"end": "(?=[,);}\\]=]|$|;|(?:^\\s*(?:abstract|async|class|const|declare|enum|export|function|import|interface|let|module|namespace|return|type|var)\\b))",
"end": "(?=[,);}\\]=>]|$|;|(?:^\\s*(?:abstract|async|class|const|declare|enum|export|function|import|interface|let|module|namespace|return|type|var)\\b))",
"patterns": [
{
"include": "#expression"

Some files were not shown because too many files have changed in this diff Show more