Skip to content

포매터, 언어 서버, 에디터 ​

English.

네 TypeScript 패키지가 템플릿 작성을 지원한다.

  • packages/language(@polyspec/template-language): 언어 서비스 openDocument()(에디터 지원), 포매터 format()(포맷), 명령줄 도구 template-fmt, 적합성 명령 template-language.
  • packages/lsp(@polyspec/template-lsp): 언어 서비스의 어댑터인 Language Server Protocol 서버 template-lsp.
  • packages/codemirror(@polyspec/template-codemirror): 언어 서비스의 결과를 CodeMirror 편집기에 등록하는 CodeMirror 6 확장 template()(CodeMirror 6 어댑터).
  • packages/vscode(polyspec-template): 언어 정의와 HTML 호스트의 TextMate 문법을 갖고, 확장 안에 포함한 언어 서버에 연결해 기능을 제공하는 VS Code 확장. 템플릿 규칙을 갖지 않는다.

언어 서비스만 템플릿 문법을 @polyspec/template에서 가져온다. 언어 서비스와 포매터는 파서가 돌려주는 태그 범위, 태그 본문, 그리고 파서가 EDT-8로 분류한 토큰을 사용하며, 자체 어휘 표를 선언하지 않는다(packages/language/tests/lexical-tables.test.ts). 서버, CodeMirror 어댑터, 확장은 언어 서비스를 호출한다. 확장은 템플릿 태그를 문법이 아니라 서버의 시맨틱 토큰으로 칠한다.

포매터 ​

포맷 스타일, 들여쓰기, 포매터가 유지하는 부분, 안전 불변식은 명세의 규칙이다(포맷, FMT-3~FMT-15). 모든 구현이 이 규칙으로 포맷하고, 적합성 케이스가 그 결과를 비교한다(CNF-18). 이 절은 @polyspec/template-language의 TypeScript 포매터 format()을 설명한다.

format(source, options)는 옵션 indent(FMT-9, 기본값 공백 두 개, null은 들여쓰기를 유지한다), templateBlocks('indent' 또는 'flat'), delimiters(엔진 구분자, FMT-8), name(오류의 템플릿 이름), range(범위 안에 완전히 들어가는 태그와 범위 안에서 시작하는 줄만 포맷한다)를 받는다. @polyspec/template의 analyze()로 소스를 파싱하고, 파서의 태그 본문과 토큰으로 각 태그를 포맷하며, 다음 결과 중 하나를 돌려준다(FMT-14).

결과조건
{ ok: true, text, changed }소스가 파싱되고 포맷한 AST가 소스 AST와 같다
{ ok: false, error: { reason: 'parse', code, line, col, message } }소스가 파싱되지 않는다
{ ok: false, error: { reason: 'invariant', code: null, line, col, message } }포맷한 텍스트가 파싱되지 않거나 다른 AST로 파싱된다
{ ok: false, error: { reason: 'html', code: null, line, col, message } }HTML 구조의 짝이 맞지 않는다(FMT-12)

출력이 HTML이 아닌 템플릿은 indent: null로 포맷한다.

불변식 테스트(packages/language/tests/invariant.test.ts)는 tests/cases(options.json의 구분자 적용), tests/fixtures, examples, 포매터 픽스처 아래의 모든 .tpl 파일을 indent: null로 한 번, 기본 들여쓰기로 한 번 포맷한다. 파싱되는 파일마다 indent: null은 span을 제외한 AST와 태그 밖의 텍스트가 같아야 한다. 기본 들여쓰기는 html 오류이거나, 줄 앞 공백과 탭을 제거한 상태에서 AST와 태그 밖의 텍스트가 같아야 한다. 둘 다 두 번째 실행에서 바뀌지 않아야 한다(FMT-13, FMT-15). packages/language/tests/conformance.test.ts는 모든 적합성 케이스의 결과를 그 expected.format.json과 비교한다.

명령 template-language format [--indent none|tab|N] [--delimiters OC] FILE(packages/language/bin/template-language.mjs)는 적합성 계약에서 TypeScript의 format 명령이다(CNF-4, CNF-5). template-fmt처럼 node:util parseArgs로 옵션을 파싱한다. 포맷한 텍스트를 출력하거나, 오류를 표준 오류에 JSON으로 쓰고 종료 상태 2로 끝난다.

명령줄 ​

sh
make install-cli
template-fmt --help
template-fmt page.tpl
template-fmt --check templates
template-fmt --write templates
template-fmt < page.tpl
usage: template-fmt [--write | --check] [--delimiters OC] [--indent N|tab|none]
                    [--template-blocks indent|flat] [PATH ...]
  • --write와 --check 없이 파일 하나를 주면 포맷한 텍스트를 표준 출력에 쓴다.
  • 경로가 없거나 -이면 표준 입력을 읽고 표준 출력에 쓴다.
  • 디렉터리를 주면 그 아래의 모든 .tpl 파일을 정렬 순서로 포맷하고, node_modules와 이름이 .으로 시작하는 디렉터리는 건너뛴다. 경로가 여러 개이거나 디렉터리이면 --write나 --check가 필요하다.
  • --write: 포맷되지 않은 파일을 다시 쓰고 그 경로를 출력한다.
  • --check: 아무것도 바꾸지 않고 포맷되지 않은 파일의 경로를 출력한다.
  • --indent: 들여쓰기 단위. 공백 N개(1~8), tab, 또는 indent: null(모든 줄이 들여쓰기를 유지)인 none이다. 기본값은 2다.
  • --template-blocks: templateBlocks 옵션인 indent(기본값) 또는 flat.
  • 파싱되지 않거나, 포맷한 AST가 다르거나, HTML 구조의 짝이 맞지 않는 파일(FMT-14)은 표준 오류에 path:line:col: LABEL: message 형식으로 출력하고 바꾸지 않는다. LABEL은 파서 오류 코드, formatted AST differs 또는 HTML structure다.
종료 상태의미
0성공. --check에서는 모든 파일이 포맷되어 있다
1--check가 포맷되지 않은 파일을 찾았다
2파싱되지 않는 파일, 다른 AST, 짝이 맞지 않는 HTML 구조, 읽을 수 없는 경로 또는 잘못된 인자가 있다

make install-cli는 명령 template-fmt를 symbolic link 없이 prefix CLI_PREFIX(기본값 ~/.local) 아래에 설치한다. scripts/install-cli.mts는 build된 formatter package와 이 checkout의 template package의 사본을 bin link 없이 설치하는 npm project <prefix>/lib/polyspec-template-fmt와, 복사된 formatter package의 진입점을 절대 경로로 node에 넘겨 실행하는 실행 script <prefix>/bin/template-fmt를 쓴다. 설치는 <prefix>/lib/.polyspec-template-fmt.next-<pid>에서 준비되어 rename으로 project를 대체하고, script는 임시 파일과 rename으로 쓰이므로, 실패한 설치는 이전 설치를 그대로 둔다. <prefix>/bin을 PATH에 둔다. make build-language 뒤에 다시 실행해 바뀐 build를 설치하며, make uninstall-cli는 project와 script를 지운다.

언어 서버 ​

sh
make build-lsp
node packages/lsp/bin/template-lsp.mjs --stdio

template-lsp는 Language Server Protocol 클라이언트가 있는 에디터에 표준 입력과 출력으로 Language Server Protocol을 제공한다. 클라이언트는 .tpl 문서에 대해 이 명령을 실행한다. 서버는 EDT-14(에디터 지원)의 기능을 제공한다. 진단, 시맨틱 토큰, 접기 범위, 문서 강조, 문서와 범위 포맷, \n, >, } 뒤의 타이핑 들여쓰기, 요청 polyspec-template/tagRanges와 polyspec-template/matchingTag다. 설정 polyspec-template.format.templateBlocks를 workspace/configuration으로 읽고, 포맷 오류는 클라이언트 로그에 경고로 쓴다. 서버는 템플릿 규칙을 갖지 않는다. 문서 버전마다 openDocument()를 한 번 호출하고, 언어 서비스의 UTF-16 문자열 인덱스를 프로토콜 위치로 변환한다. Language Server Protocol 클라이언트가 넘기는 인자 --stdio는 받아들이며 효과가 없다.

VS Code 확장 ​

sh
make vscode-package
make vscode-install
code --list-extensions --show-versions | grep polyspec

make vscode-package는 src/extension.ts와 vscode-languageclient를 한 파일 dist/extension.cjs로 만들고, 서버 진입점 @polyspec/template-lsp/server와 @polyspec/template-language, @polyspec/template을 한 파일 dist/server.cjs로 만든 뒤, packages/vscode/dist/polyspec-template.vsix를 만든다. 설치한 확장은 실행할 때 저장소가 필요 없다. make vscode-install은 --force를 붙여 code --install-extension을 실행한다.

확장은 언어 서버 template-lsp에 연결해 동작한다(EDT-15). 확장의 언어 클라이언트(vscode-languageclient)가 VS Code의 Node.js 런타임으로 dist/server.cjs를 실행하고, 표준 입력과 출력으로 서버와 통신한다. 클라이언트는 URI scheme과 관계없이 언어 polyspec-template의 열린 문서마다 텍스트를 서버에 보낸다.

확장과 서버는 열린 문서의 텍스트만 읽고 작업 공간의 코드를 실행하지 않으므로 확장은 capabilities.untrustedWorkspaces.supported를 선언한다. 이 선언이 없으면 VS Code는 Restricted Mode에서 문법을 포함한 확장 전체를 비활성화하고, 신뢰하지 않은 폴더의 .tpl 파일은 일반 텍스트로 열린다. 확장은 capabilities.virtualWorkspaces도 선언한다. 서버는 문서 텍스트를 프로토콜로만 받고 파일 시스템을 읽지 않으므로, 가상 작업 공간의 문서도 파일과 같이 분석하며 그 URI가 진단의 템플릿 이름이 된다. 확장은 main 진입점만 있고 browser 진입점이 없으므로, Node.js 확장 호스트가 없는 VS Code for the Web은 확장을 실행하지 않는다. VS Code는 Electron 빌드에 포함된 Node.js로 확장과 서버를 실행한다. VS Code 1.138.0은 Node.js 24.18.1을 가진 Electron 42.10.0을 사용하므로 두 번들의 대상은 node24이고, 확장은 engines.vscode ^1.138.0과 그 Electron의 Node.js인 engines.node >=24.18.1을 선언한다. kit의 Node.js pin 26.12.0은 다른 package의 하한이다. scope가 packages/vscode이고 channel이 24.18.1인 config/toolchain.json의 exception이 toolchain 검사가 확장의 하한을 받아들이게 한다.

확장은 .tpl 파일에 언어 polyspec-template을 등록하고 다음을 제공한다.

  • 주석 토글을 위한 블록 주석 {* *}
  • { }, [ ], ( ), 따옴표, <!-- -->의 괄호 쌍과 자동 닫기
  • 서버의 문서 포맷과 범위 포맷. 들여쓰기 단위는 편집기의 것이다. 공백 tabSize개이며, insertSpaces가 꺼져 있으면 탭이다. 설정 polyspec-template.format.templateBlocks(indent 또는 flat)가 templateBlocks 옵션이며, 서버는 이를 workspace/configuration으로 읽는다. 범위 포맷은 선택 범위 안에 완전히 들어가는 태그를 포맷하고 범위 안에서 시작하는 줄을 들여쓴다. format()이 오류를 돌려주면 서버는 편집을 돌려주지 않고 출력 채널 Polyspec Template에 위치를 경고로 기록한다.
  • 타이핑 중 들여쓰기: Enter, >, } 뒤에 서버가 현재 줄의 들여쓰기를 lineIndentation()(EDT-13)으로 정한다. VS Code는 editor.formatOnType이 켜져 있을 때만 이를 요청하므로, 확장은 설정 기본값으로 "[polyspec-template]": { "editor.formatOnType": true }를 제공한다. 이 언어에 대한 사용자 설정이나 작업 공간 설정이 이를 덮어쓴다. Enter 뒤의 빈 줄은 그 깊이의 들여쓰기를 갖는다.

진단과 짝 태그 ​

서버는 진단, 토큰, 태그 범위, 구성, 접기 범위, 짝 태그를 언어 서비스의 openDocument()에서 얻는다(에디터 지원, EDT-4~EDT-11). 언어 서비스의 테스트는 모든 적합성 사례에서 구성을 AST의 If, For, IfBlock span과 분기 span과 비교한다.

  • 진단: 서버는 문서의 모든 버전에 대해 E_PARSE_UNCLOSED_BLOCK, E_PARSE_UNEXPECTED_CLOSE, E_PARSE_ELSE_OUTSIDE_BLOCK 같은 파싱 오류를 오류 코드와 함께 파서 위치에 게시한다. 문서가 파싱되거나 닫히면 진단을 지운다.
  • 시맨틱 토큰: 서버는 텍스트의 템플릿 부분을 EDT-8의 토큰 종류로 분류한다. 태그 밖의 텍스트는 문법의 HTML 색을 유지한다. 확장은 VS Code가 정의하지 않은 종류를 semanticTokenTypes로 제공한다. 상위 종류가 keyword인 delimiter와 상위 종류가 string인 path다. semanticTokenScopes는 delimiter를 keyword.control.tag.begin.polyspec-template에, keyword를 keyword.control.polyspec-template에, path를 string.unquoted.path.polyspec-template에 대응시키므로, 시맨틱 토큰 규칙이 없는 테마도 구분자와 기호를 keyword.control로 칠한다. 기본값 configuredByTheme는 시맨틱 강조를 켜지 않는 테마에서 태그를 칠하지 않으므로, 확장은 "[polyspec-template]": { "editor.semanticHighlighting.enabled": true }를 설정 기본값으로 제공한다. 이 언어에 대한 사용자나 작업 공간 설정이 이를 덮어쓴다.
  • 태그 배경: 주석을 뺀 모든 태그에 어두운 테마에서는 #16351c, 밝은 테마에서는 #e3f6dd인 진한 초록 배경을 칠한다. 이 배경은 편집기 배경과 밝기가 다르고(Dark 2026에서 ΔL* +13.4, Light 2026에서 5.1), Dark 2026의 모든 문법 색을 4.5:1 이상으로 유지한다. 가장 낮은 것은 키워드 기호의 4.8:1이다. #044700처럼 채도가 더 높은 초록은 더 눈에 띄지만 기호를 4.0:1로 낮추고, 어두운 테마의 검정처럼 편집기와 밝기가 같은 색은 구별되지 않는다. 확장은 템플릿 편집기가 보일 때와 마지막 변경 250 ms 뒤에 polyspec-template/tagRanges를 요청하고, 요청하는 동안 문서가 바뀌지 않았을 때만 결과를 칠한다. 문서가 파싱되지 않는 동안에는 파서가 오류 전까지 받아들인 태그에 배경을 칠한다(EDT-6). 확장은 API로 tagRanges(document)를 돌려주며(vscode.extensions.getExtension('polyspec.polyspec-template').exports), 이 함수는 확장이 칠하는 범위를 준다. VS Code에는 편집기의 장식을 읽는 API가 없으므로 통합 테스트는 이 범위를 여기서 읽는다.
  • 강조: 커서가 여는 태그, 분기 태그, 닫는 태그에 있으면 같은 구성의 모든 태그를 강조한다.
  • 접기: 닫는 태그가 뒤의 줄에 있는 구성은 여는 태그의 줄부터 닫는 태그 앞 줄까지 접힌다.
  • 명령 Go to Matching Template Tag(polyspec-template.goToMatchingTag)는 polyspec-template/matchingTag를 요청해 커서를 커서 아래 구성의 다음 태그로 옮기고, 마지막 태그에서는 여는 태그로 옮긴다. 태그 밖에서는 커서를 감싸는 가장 안쪽 구성의 다음 태그로 옮긴다. 단축키는 macOS에서 Cmd+Alt+\, Windows와 Linux에서 Ctrl+Alt+\이며 템플릿 편집기에서만 동작한다. 통합 테스트는 macOS에서 VS Code 1.138.0의 기본 단축키가 Cmd+Alt+\를 다른 명령에 연결하지 않는지 검사한다.

문서가 파싱되지 않는 동안 강조, 접기, 명령은 닫힌 구성만 사용한다(EDT-6). 확장은 템플릿 분기를 가로지르는 HTML 요소의 균형을 검사하지 않는다. HTML 구조는 VS Code의 HTML 기능에 맡긴다.

문법 ​

문법 text.html.polyspec-template은 <style>의 CSS와 <script>의 JavaScript를 포함하는 text.html.basic만 포함하고, 템플릿 규칙을 갖지 않는다. 서버는 analyze의 파서로 태그를 읽으므로, 서버의 시맨틱 토큰이 텍스트, 속성 값 안과 속성 사이, CSS, JavaScript, HTML 주석의 모든 태그를 칠한다. 정규 표현식 문법은 파서 문맥이 없다. 그 문법은 {% delimiter ..} 지시문 뒤의 태그, 오류인 태그 본문, 따옴표 없는 속성 값 안의 태그처럼 HTML 패턴이 덮는 태그에서 analyze와 달랐다. 서버가 응답하기 전에는 태그가 그 위치의 HTML 색으로 보인다.

CodeMirror 6 어댑터 ​

packages/codemirror(@polyspec/template-codemirror)는 @codemirror/lang-html 위에 만든 CodeMirror 6 확장이다(EDT-16). 모든 결과를 언어 서비스의 openDocument()에서 얻고, 위치 외에는 아무것도 변환하지 않으며, 템플릿 규칙을 갖지 않는다(에디터 지원). CodeMirror와 언어 서비스는 모두 문서 텍스트의 UTF-16 문자열 인덱스를 쓰므로, 언어 서비스의 위치가 곧 CodeMirror의 위치다. @codemirror/state, @codemirror/view, @codemirror/language, @codemirror/lint, @codemirror/lang-html, @codemirror/commands는 peer dependency이므로 어댑터는 편집기가 쓰는 각 패키지의 인스턴스 하나를 쓴다.

ts
import { defaultKeymap } from '@codemirror/commands';
import { EditorState } from '@codemirror/state';
import { EditorView, keymap } from '@codemirror/view';
import { goToMatchingTag, template } from '@polyspec/template-codemirror';

new EditorView({
  parent: document.querySelector('#editor') as HTMLElement,
  state: EditorState.create({
    doc: '<ul>\n  {@ item = items}\n    <li>{= item.name}</li>\n  {/}\n</ul>\n',
    extensions: [
      keymap.of([{ key: 'Mod-Alt-\\', run: goToMatchingTag }, ...defaultKeymap]),
      template({ templateBlocks: 'indent', name: 'list.tpl' }),
    ],
  }),
});

template(options)가 확장을 돌려준다. 옵션은 다음과 같다.

옵션의미
templateBlocksindent(기본값) 또는 flat. 들여쓰기 서비스와 formatTemplate의 templateBlocks 옵션이다
delimiters두 문자로 된 엔진 구분자 옵션. 기본값은 {}이다
name진단에 쓰는 템플릿 이름. 기본값은 template.tpl이다

확장은 다음을 가진다.

  • @codemirror/lang-html의 HTML 언어
  • 상태의 텍스트에 대한 TemplateDocument를 갖는 상태 필드 templateDocument. 문서가 바뀔 때 텍스트를 한 번 분석하고, 아래의 모든 기능이 그 분석을 읽는다.
  • EDT-8의 모든 토큰에 붙는 클래스 cm-template-<type>의 마크. 토큰 마크는 문법 강조보다 우선순위가 높으므로 그 요소가 HTML 강조의 요소 안에 놓이고, HTML 속성 값 안의 템플릿 토큰도 템플릿 색을 유지한다.
  • 모든 태그 범위에 붙는 클래스 cm-template-tag의 마크(EDT-9). 우선순위가 가장 낮으므로 그 요소가 태그의 토큰 마크를 감싼다.
  • 주 커서 아래 구성의 모든 태그에 붙는 클래스 cm-template-highlight의 마크(highlights, EDT-11)
  • @codemirror/lint의 linter를 통한 진단. 심각도는 error, 출처는 polyspec-template, 메시지는 CODE: message이다. 예를 들면 E_PARSE_UNCLOSED_BLOCK: block is not closed before the end of the file이다(EDT-7). templateDiagnostics(state)는 상태에 대해 같은 진단을 돌려준다.
  • foldingRanges로 만든 foldService 접기(EDT-10). 한 줄을 접으면 여는 태그 다음 줄부터 닫는 태그 앞 줄까지 숨긴다. CodeMirror는 한 줄에 범위 하나를 접는다. 한 줄에서 여러 구성이 시작하면 가장 늦게 끝나는 구성을 접는다.
  • 편집기의 indentUnit과 templateBlocks로 구한 그 줄의 lineIndentation 열 너비를 돌려주는 indentService(EDT-13). Enter가 줄을 나누면 서비스는 줄을 나눈 텍스트를 분석하므로, 새 줄과 그 줄로 옮겨진 텍스트는 타이핑하는 줄의 들여쓰기를 얻는다. 언어 서비스가 요구하는 대로 들여쓰기 단위는 공백의 연속이거나 탭 하나여야 한다.
  • formatTemplate을 실행하는 Shift+Alt+F

기본 테마(EditorView.baseTheme)가 마크에 색을 칠한다. &dark는 어두운 테마의 편집기에, &light는 그 밖의 편집기에 적용된다. 태그 배경은 어두운 테마에서 #16351c, 밝은 테마에서 #e3f6dd이고, 강조는 #7ee787(어두운 테마)과 #1a7f37(밝은 테마)의 1픽셀 외곽선이며, 주석은 기울임꼴이다. 같은 클래스를 쓰는 테마가 이 규칙을 덮어쓴다.

클래스어두운 테마밝은 테마
cm-template-delimiter#ff7b72#cf222e
cm-template-keyword#ff7b72#cf222e
cm-template-variable#ffa657#953800
cm-template-property#79c0ff#0550ae
cm-template-function#d2a8ff#8250df
cm-template-string#a5d6ff#0a3069
cm-template-number#79c0ff#0550ae
cm-template-operator#e6edf3#1f2328
cm-template-comment#8b949e#6e7781
cm-template-path#a5d6ff#0a3069

명령은 다음과 같다.

명령동작
formatTemplate편집기의 indentUnit과 templateBlocks 옵션으로 format()(EDT-12)을 실행해 문서를 바꾼다. format()이 오류를 돌려주면 아무것도 바꾸지 않고 false를 돌려준다. Shift+Alt+F가 이 명령을 실행한다.
goToMatchingTag주 커서를 그 위치의 matchingTag(EDT-11)로 옮기고, 짝 태그가 없으면 false를 돌려준다. 확장은 이 명령에 키를 연결하지 않는다. 예제는 Mod-Alt-\를 연결한다.

Shift+Alt+F는 키맵 항목 Shift-Alt-f가 아니라 물리 키 F(KeyboardEvent.code KeyF)에 연결한다. 키맵 항목은 입력된 문자와 비교하는데, macOS에서 Option+Shift+F는 Ï를 입력하므로 그 항목은 macOS에서 실행되지 않는다.

검증 ​

sh
make test-language
make test-lsp
make format-check
make test-codemirror
make test-vscode
make test-vscode-integration
make format-external-check TEMPLATE_SOURCE_ROOT=/path/to/templates

make test-lsp는 빌드한 명령 template-lsp를 자식 프로세스로 실행하고, vscode-jsonrpc로 표준 입력과 출력을 통해 프로토콜을 주고받으며, 모든 에디터 픽스처(EDT-17)에 대해 게시된 진단, 디코딩한 시맨틱 토큰, 태그 범위, 접기 범위, 픽스처 위치에서의 강조와 짝 태그, 두 templateBlocks 방식에서 모든 줄의 타이핑 들여쓰기, 포맷한 텍스트를 기대 결과와 비교한다. 또한 바이트 순서 표시, CRLF 줄 끝, 기본 다국어 평면 밖의 문자가 있는 텍스트를 언어 서비스와 비교하고, 선언한 기능, multilineTokenSupport가 없는 클라이언트에 대한 여러 줄 토큰의 분할, 설정 요청, 포맷 오류, 범위 포맷, 편집이 없는 타이핑 포맷을 검사한다.

make test-vscode는 매니페스트를 테스트하고, 문법이 text.html.basic만 포함하는지, dist/extension.cjs가 vscode와 Node.js 내장 모듈만, dist/server.cjs가 Node.js 내장 모듈만 불러오는지, .vsix가 둘 다 담는지, 매니페스트가 서버 legend의 토큰 종류 가운데 VS Code가 정의하지 않은 것을 모두 제공하는지, 번들한 서버가 표준 입력과 출력으로 진단을 게시하고 polyspec-template/tagRanges, polyspec-template/matchingTag, 타이핑 포맷에 응답하는지 검사한다. 언어 클라이언트는 VS Code 안에서만 실행되므로 통합 테스트가 이를 검사한다. make test-vscode-integration은 .vsix를 빌드하고, make install 뒤에 make install-vscode가 kit의 VS Code installer(@polyspec/kit/install-vscode)로 var/tools/vscode(tests/integration/run.mjs의 option --vscode)에 설치한 engines.vscode의 최소 버전 VS Code 1.138.0을 쓰며, VS Code 명령줄로 .vsix를 새 확장 디렉터리에 설치한다. 사용자 설치와 같이 workspace trust를 켜고 테스트 폴더를 신뢰하지 않은 상태로 VS Code를 실행해 .tpl 문서가 언어 polyspec-template으로 열리는지, 설치한 확장이 활성화되어 번들한 서버를 시작하는지, 이 언어에서 editor.semanticHighlighting.enabled가 켜져 있는지, 확장이 칠하는 태그 범위가 파싱되지 않는 문서에서도 주석을 뺀 모든 태그를 덮는지, vscode.provideDocumentSemanticTokens가 텍스트의 태그와 HTML 속성 값 안의 태그에 서버의 토큰 종류를 돌려주는지, 이 언어에서 editor.formatOnType이 켜져 있고 }와 Enter를 입력하면 현재 줄을 들여쓰는지, 닫히지 않은 {?가 그 위치에 진단을 만드는지, 문서 강조, 접기 범위, Go to Matching Template Tag가 한 구성을 따르는지, 단축키가 비어 있는지, vscode.executeFormatDocumentProvider가 예제에 기대한 편집을 돌려주고 포맷된 문서와 파싱되지 않는 문서에는 편집을 돌려주지 않는지 검사한다. 두 번째 실행은 설치한 매니페스트에서 capabilities를 지우고 VS Code가 그 작업 공간에서 확장을 비활성화하는지 요구한다. 이것으로 첫 실행이 capability 누락을 찾아낸다는 것을 보인다. profile 설치와 VS Code 실행은 각각 시간 제한이 없는 단계다(tests/integration/step.mjs). 단계는 시작, 실행 중 10 s마다 한 줄, 경과 시간이 붙은 결과를 출력하고, 설치와 실행은 VS Code의 출력을 도착하는 대로 출력한다. 설치는 exit code로, 실행은 exit code나 suite 결과로 판단한다. check마다 20 s의 timeout이 있고, suite는 check마다 [suite] start - <check>를 출력한 뒤 [suite] ok - <check> (<ms> ms)나 [suite] not ok - <check> (<ms> ms)를 출력한다. 입력한 줄의 format처럼 확장을 기다리는 check에는 자기 시간 제한이 없다(tests/integration/suite/wait.cjs). check의 timeout만 기다림을 끝내며, 그 error는 check가 기다린 것을 기대한 text, 실제 text와 함께 밝힌다(T20.1-7). 실행은 suite 결과로 끝난다. VS Code 1.138은 suite가 [suite] N of M checks passed를 출력한 뒤 종료하는 데 몇 분이 걸리기도 하므로, 그 줄 뒤 20 s 안에 종료하지 않으면 process group과 함께 kill한다. 20 s는 suite 결과가 출력된 뒤에 시작하며, VS Code를 끝낼 뿐 결과를 정하지 않는다.

integration 실행은 VS Code를 내려받지 않는다. 설치된 사본이 없으면 run make install-vscode와 함께 실패한다. VS Code는 integration test를 실행하는 곳에만 필요하므로 make install은 이것을 내려받지 않고, CI에서는 integration test를 실행하는 job만 make install-vscode를 실행한다(T20.1-6). installer는 사본을 var/tools/vscode/.next-<pid>에 내려받고 vscode-<platform>-<version>으로 rename하므로, 읽는 쪽은 사본이 없거나 완전한 사본을 찾으며, 있는 사본은 다시 내려받지 않는다. 설치된 사본은 바뀌지 않으므로 lock이 필요 없다(T20.1-4).

make test-codemirror는 어댑터를 빌드하고 두 종류의 테스트를 실행한다. Vitest 테스트는 packages/language/tests/editor의 모든 에디터 픽스처마다 DOM 없이 EditorState를 만들고, templateDiagnostics의 진단, 상태의 decoration 집합에 있는 토큰 마크와 태그 마크, fold service의 접기 범위, 모든 조회 위치의 강조 마크와 goToMatchingTag 목적지, templateBlocks indent와 flat 및 공백 두 개의 들여쓰기 단위로 구한 모든 줄의 들여쓰기 서비스 결과, formatTemplate의 결과를 픽스처의 기대 결과와 비교한다(EDT-17). 다른 테스트는 한 줄을 입력하고 insertNewlineAndIndent로 Enter를 누르며, 옵션, formatTemplate의 들여쓰기 단위, 텍스트마다 분석 하나를 재사용하는지 검사한다. Playwright 테스트는 tests/browser/page.ts를 esbuild로 packages/codemirror/dist/browser/page.js에 번들하고 빈 Chromium 페이지에 불러온 뒤, <ul>, Enter, {@ x = xs}, Enter를 입력한 뒤의 들여쓰기, 토큰 클래스, HTML 속성 값 안의 템플릿 색, 밝은 테마와 어두운 테마의 태그 배경, 닫히지 않은 블록의 lint 진단, 포맷되는 텍스트와 파싱되지 않는 텍스트에서의 Shift+Alt+F를 검사한다.

CI job editor는 make editor-boundary-check test-language format-check test-lsp test-codemirror test-vscode를 실행하고, VS Code가 화면을 요구하므로 make test-vscode-integration을 xvfb-run의 가상 X 서버에서 실행한다.

make check가 test-language, test-lsp, test-codemirror, format-check, test-vscode, test-vscode-integration을 실행한다. make format-external-check는 명시한 외부 템플릿 트리에도 불변식 테스트를 실행한다.