docs: upload code files and config
This commit is contained in:
@@ -0,0 +1,212 @@
|
||||
# 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 = /<script\b[^>]*>([\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 = /<style\b[^>]*>([\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<AdapterResult> {
|
||||
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. 合并所有结果
|
||||
```
|
||||
|
||||
**提取器正则规则**:
|
||||
|
||||
| 代码块类型 | 正则 | 目标适配器 |
|
||||
|-----------|------|-----------|
|
||||
| `<script>` | `/<script\b[^>]*>([\s\S]*?)<\/script\s*>/gi` | ESLint |
|
||||
| `<style>` | `/<style\b[^>]*>([\s\S]*?)<\/style\s*>/gi` | Stylelint |
|
||||
| `<%=? %>` | `/<%=?([\s\S]*?)%>/g` | PMD |
|
||||
|
||||
- 行号偏移修正:`lineOffset` 从提取位置之前的换行符数计算
|
||||
- 根据 `linters.<language>` 配置决定是否启用对应子适配器
|
||||
- 组合状态:任一子适配器不可用则标记 `tool-unavailable`
|
||||
|
||||
---
|
||||
|
||||
## 验收
|
||||
|
||||
- [ ] 2 个文件创建完成
|
||||
- [ ] `npm run compile` 通过
|
||||
- [ ] `npm run lint` 通过
|
||||
Reference in New Issue
Block a user