All files / src/adapters jsp.ts

30.35% Statements 34/112
100% Branches 1/1
20% Functions 1/5
30.35% Lines 34/112

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 1131x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x                       1x 1x 1x 1x 1x 1x 1x 1x 1x 1x                                                                                                                     1x 1x               1x 1x     1x  
import * as vscode from 'vscode';
import type { LinterAdapter, LinterDiagnostic, AdapterResult } from '../types';
import { PmdAdapter } from './pmd';
import { ESLintAdapter } from './eslint';
import { StylelintAdapter } from './stylelint';
import { extractJspSections, type JspSection } from '../jsp/jsp-extractor';
import { getLinterForLanguage } from '../config';
import { mockDocument } from '../utils/mockDocument';
 
const WRAP_TEMPLATES: Record<NonNullable<JspSection['scriptletKind']>, {
  header: string;
  footer: string;
  headerLines: number;
}> = {
  statement: { header: 'package jsp;\nclass JspScriptlet {\n  void run() {\n', footer: '\n  }\n}', headerLines: 3 },
  expression: { header: 'package jsp;\nclass JspScriptlet {\n  Object run() {\n    return\n', footer: '\n  }\n}', headerLines: 4 },
  declaration: { header: 'package jsp;\nclass JspScriptlet {\n', footer: '\n}', headerLines: 2 },
};
 
function wrapJavaSection(section: JspSection): { code: string; headerLines: number } {
  if (section.language !== 'java' || !section.scriptletKind) {
    return { code: section.code, headerLines: 0 };
  }
  const tmpl = WRAP_TEMPLATES[section.scriptletKind];
  let body = section.code;
  if (section.scriptletKind === 'expression' && body.trim() !== '' && !body.trim().endsWith(';')) {
    body += ';';
  }
  return { code: tmpl.header + body + tmpl.footer, headerLines: tmpl.headerLines };
}
 
export class JspAdapter implements LinterAdapter {
  id = 'jsp';
  supportedLanguages = ['jsp', 'html'];
 
  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 jsEnabled = getLinterForLanguage('javascript') !== '';
    const cssEnabled = getLinterForLanguage('css') !== '';
    const javaEnabled = getLinterForLanguage('java') !== '';

    const pmdResult = await this.pmdAdapter.checkJsp(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 { code, headerLines } = wrapJavaSection(section);
        const result = await adapter.check(mockDocument(code, section.language), workingDir);

        for (const diag of result.diagnostics) {
          const startLine = diag.range.start.line - headerLines;
          const endLine = diag.range.end.line - headerLines;
          if (startLine < 0) { continue; }
          const adjustedRange = new vscode.Range(
            startLine + section.lineOffset,
            diag.range.start.character,
            endLine + 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;
  }
}