# Step 07 — Phase 2.6: JSP 适配器 **依赖**: Step 03, 04, 06(需要 PMD / ESLint / Stylelint 适配器) **参考设计**: §3.5 ## 目标 实现 JSP 组合适配器。将 JSP 文件拆分后分发检查,合并结果。 ## 新建文件 | # | 文件 | 说明 | |---|------|------| | 1 | `src/jsp/jsp-extractor.ts` | JSP 内嵌代码块提取器 | | 2 | `src/adapters/jsp.ts` | `JspAdapter`(组合适配器) | --- ## 1. `src/jsp/jsp-extractor.ts` ```typescript export interface JspSection { language: 'javascript' | 'css' | 'java'; code: string; lineOffset: number; sourceStart: number; sourceEnd: number; } export function extractJspSections(content: string): JspSection[] { const sections: JspSection[] = []; const scriptRegex = /]*>([\s\S]*?)<\/script\s*>/gi; let match: RegExpExecArray | null; while ((match = scriptRegex.exec(content)) !== null) { const code = match[1]; const beforeMatch = content.substring(0, match.index); const lineOffset = beforeMatch.split('\n').length - 1; sections.push({ language: 'javascript', code, lineOffset, sourceStart: match.index, sourceEnd: match.index + match[0].length, }); } const styleRegex = /]*>([\s\S]*?)<\/style\s*>/gi; while ((match = styleRegex.exec(content)) !== null) { const code = match[1]; const beforeMatch = content.substring(0, match.index); const lineOffset = beforeMatch.split('\n').length - 1; sections.push({ language: 'css', code, lineOffset, sourceStart: match.index, sourceEnd: match.index + match[0].length, }); } const scriptletRegex = /<%=?([\s\S]*?)%>/g; while ((match = scriptletRegex.exec(content)) !== null) { const code = match[1]; const beforeMatch = content.substring(0, match.index); const lineOffset = beforeMatch.split('\n').length - 1; sections.push({ language: 'java', code, lineOffset, sourceStart: match.index, sourceEnd: match.index + match[0].length, }); } return sections; } ``` --- ## 2. `src/adapters/jsp.ts` ```typescript import * as vscode from 'vscode'; import { LinterAdapter, LinterDiagnostic, AdapterResult } from '../types'; import { PmdAdapter } from './pmd'; import { ESLintAdapter } from './eslint'; import { StylelintAdapter } from './stylelint'; import { extractJspSections, JspSection } from '../jsp/jsp-extractor'; import { getLinterConfig } from '../config'; export class JspAdapter implements LinterAdapter { id = 'jsp'; supportedLanguages = ['jsp']; private pmdAdapter = new PmdAdapter(); private eslintAdapter = new ESLintAdapter(); private stylelintAdapter = new StylelintAdapter(); async check(document: vscode.TextDocument, workingDir: string): Promise { const allDiagnostics: LinterDiagnostic[] = []; const errors: string[] = []; const config = getLinterConfig(); const jsEnabled = config.languageMap.javascript !== ''; const cssEnabled = config.languageMap.css !== ''; const javaEnabled = config.languageMap.java !== ''; const pmdResult = await this.pmdAdapter.check(document, workingDir); allDiagnostics.push(...pmdResult.diagnostics); if (pmdResult.status !== 'ok') { errors.push(`PMD: ${pmdResult.errorMessage ?? pmdResult.status}`); } const sections = extractJspSections(document.getText()); for (const section of sections) { const isEnabled = (section.language === 'javascript' && jsEnabled) || (section.language === 'css' && cssEnabled) || (section.language === 'java' && javaEnabled); if (!isEnabled) { continue; } const adapter = this.getAdapter(section.language); if (!adapter) { continue; } try { const virtualDoc = await vscode.workspace.openTextDocument({ content: section.code, language: section.language, }); const result = await adapter.check(virtualDoc, workingDir); for (const diag of result.diagnostics) { const adjustedRange = new vscode.Range( diag.range.start.line + section.lineOffset, diag.range.start.character, diag.range.end.line + section.lineOffset, diag.range.end.character, ); allDiagnostics.push({ ...diag, range: adjustedRange }); } if (result.status !== 'ok') { errors.push(`${section.language}: ${result.errorMessage ?? result.status}`); } } catch (err) { errors.push(`${section.language}: ${err instanceof Error ? err.message : String(err)}`); } } const hasErrors = errors.length > 0; const hasUnavailable = errors.some(e => e.includes('未安装') || e.includes('tool-unavailable')); return { diagnostics: allDiagnostics, status: hasErrors ? (hasUnavailable ? 'tool-unavailable' : 'execution-failed') : 'ok', errorMessage: errors.join('; '), }; } private getAdapter(language: string): LinterAdapter | null { switch (language) { case 'javascript': return this.eslintAdapter; case 'css': return this.stylelintAdapter; case 'java': return this.pmdAdapter; default: return null; } } isAvailable(): boolean { return true; } } ``` --- ## 关键逻辑 **JspAdapter.check() 三步流程**: ``` 1. 调用 PmdAdapter.check(document) → JSP 规范检查 2. extractJspSections(document.getText()) → 提取内嵌代码块 3. 对每个 section: a. 按 language 选择对应适配器(ESLint/Stylelint/PMD) b. 创建虚拟文档 (vscode.workspace.openTextDocument) c. 调用 adapter.check(virtualDoc) d. 修正行号偏移 (section.lineOffset) 4. 合并所有结果 ``` **提取器正则规则**: | 代码块类型 | 正则 | 目标适配器 | |-----------|------|-----------| | `