Jelajahi Sumber

Merge pull request #28675 from overleaf/dp-pdf-preview-output-files-typescript-2

Convert output-files.js to typescript

GitOrigin-RevId: 32eb509f491cfd53de7f1b21b97861ba421566a5
David 10 bulan lalu
induk
melakukan
1b5887d97f

+ 89 - 45
services/web/frontend/js/features/pdf-preview/util/output-files.js → services/web/frontend/js/features/pdf-preview/util/output-files.ts

@@ -1,10 +1,17 @@
 import HumanReadableLogs from '../../../ide/human-readable-logs/HumanReadableLogs'
-import BibLogParser from '../../../ide/log-parser/bib-log-parser'
+import BibLogParser, {
+  BibLogEntry,
+} from '../../../ide/log-parser/bib-log-parser'
 import { enablePdfCaching } from './pdf-caching-flags'
 import { debugConsole } from '@/utils/debugging'
 import { dirname, findEntityByPath } from '@/features/file-tree/util/path'
 import '@/utils/readable-stream-async-iterator-polyfill'
 import { EDITOR_SESSION_ID } from '@/features/pdf-preview/util/metrics'
+import { LogEntry } from './types'
+import { CompileResponseData, PDFFile } from '@ol-types/compile'
+import { LatexLogEntry } from '@/ide/log-parser/latex-log-parser'
+import { Annotation } from '@ol-types/annotation'
+import { Folder } from '@ol-types/folder'
 
 // Warnings that may disappear after a second LaTeX pass
 const TRANSIENT_WARNING_REGEX = /^(Reference|Citation).+undefined on input line/
@@ -12,7 +19,11 @@ const TRANSIENT_WARNING_REGEX = /^(Reference|Citation).+undefined on input line/
 const MAX_LOG_SIZE = 1024 * 1024 // 1MB
 const MAX_BIB_LOG_SIZE_PER_FILE = MAX_LOG_SIZE
 
-export function handleOutputFiles(outputFiles, projectId, data) {
+export function handleOutputFiles(
+  outputFiles: Map<string, PDFFile>,
+  projectId: string,
+  data: CompileResponseData
+): PDFFile | null {
   const outputFile = outputFiles.get('output.pdf')
   if (!outputFile) return null
 
@@ -34,10 +45,7 @@ export function handleOutputFiles(outputFiles, projectId, data) {
     params.set('enable_pdf_caching', 'true')
   }
 
-  outputFile.pdfUrl = `${buildURL(
-    outputFile,
-    data.pdfDownloadDomain
-  )}?${params}`
+  outputFile.pdfUrl = `${buildURL(outputFile, data.pdfDownloadDomain)}?${params}`
 
   if (data.fromCache) {
     outputFile.pdfDownloadUrl = outputFile.downloadURL
@@ -53,33 +61,58 @@ export function handleOutputFiles(outputFiles, projectId, data) {
 
 let nextEntryId = 1
 
-function generateEntryKey() {
+function generateEntryKey(): string {
   return 'compile-log-entry-' + nextEntryId++
 }
 
-export const handleLogFiles = async (outputFiles, data, signal) => {
-  const result = {
+type LogResult = {
+  log: string | null
+  logEntries: {
+    errors: LogEntry[]
+    warnings: LogEntry[]
+    typesetting: LogEntry[]
+    all: LogEntry[]
+  }
+}
+export async function handleLogFiles(
+  outputFiles: Map<string, PDFFile>,
+  data: CompileResponseData,
+  signal: AbortSignal
+): Promise<LogResult> {
+  const result: LogResult = {
     log: null,
     logEntries: {
+      all: [],
       errors: [],
       warnings: [],
       typesetting: [],
     },
   }
 
-  function accumulateResults(newEntries, type) {
-    for (const key in result.logEntries) {
+  function accumulateResults(
+    newEntries: {
+      errors?: (LatexLogEntry | BibLogEntry)[]
+      warnings?: (LatexLogEntry | BibLogEntry)[]
+      typesetting?: (LatexLogEntry | BibLogEntry)[]
+      all?: (LatexLogEntry | BibLogEntry)[]
+    },
+    type?: string
+  ) {
+    for (const key of Object.keys(result.logEntries) as Array<
+      keyof typeof result.logEntries
+    >) {
       if (newEntries[key]) {
         for (const entry of newEntries[key]) {
           if (type) {
-            entry.type = newEntries.type
+            // Type casting as we are mutating LatexLogEntry | BibLogEntry into a LogEntry
+            ;(entry as LogEntry).type = type
           }
           if (entry.file) {
             entry.file = normalizeFilePath(entry.file)
           }
-          entry.key = generateEntryKey()
+          ;(entry as LogEntry).key = generateEntryKey()
         }
-        result.logEntries[key].push(...newEntries[key])
+        result.logEntries[key].push(...(newEntries[key] as LogEntry[]))
       }
     }
   }
@@ -111,7 +144,7 @@ export const handleLogFiles = async (outputFiles, data, signal) => {
     }
   }
 
-  const blgFiles = []
+  const blgFiles: PDFFile[] = []
 
   for (const [filename, file] of outputFiles) {
     if (filename.endsWith('.blg')) {
@@ -143,15 +176,15 @@ export const handleLogFiles = async (outputFiles, data, signal) => {
   return result
 }
 
-/**
- * @typedef {import('../../../../../types/annotation').Annotation} Annotation
- * @returns {Record<string, Annotation[]>}
- */
-export function buildLogEntryAnnotations(entries, fileTreeData, rootDocId) {
-  const rootDocDirname = dirname(fileTreeData, rootDocId)
+export function buildLogEntryAnnotations(
+  entries: LogEntry[],
+  fileTreeData: Folder,
+  rootDocId?: string | null
+): Record<string, Annotation[]> {
+  const rootDocDirname = rootDocId ? dirname(fileTreeData, rootDocId) : null
 
-  const logEntryAnnotations = {}
-  const seenLine = {}
+  const logEntryAnnotations: Record<string, Annotation[]> = {}
+  const seenLine: Record<number, boolean> = {}
 
   for (const entry of entries) {
     if (entry.file) {
@@ -164,12 +197,12 @@ export function buildLogEntryAnnotations(entries, fileTreeData, rootDocId) {
           logEntryAnnotations[entity._id] = []
         }
 
-        const annotation = {
+        const annotation: Annotation = {
           id: entry.key,
           entryIndex: logEntryAnnotations[entity._id].length, // used for maintaining the order of items on the same line
-          row: entry.line - 1,
+          row: (entry.line || 1) - 1,
           type: entry.level === 'error' ? 'error' : 'warning',
-          text: entry.message,
+          text: entry.message ?? '',
           source: 'compile', // NOTE: this is used in Ace for filtering the annotations
           ruleId: entry.ruleId,
           command: entry.command,
@@ -177,9 +210,9 @@ export function buildLogEntryAnnotations(entries, fileTreeData, rootDocId) {
 
         // set firstOnLine for the first non-typesetting annotation on a line
         if (entry.level !== 'typesetting') {
-          if (!seenLine[entry.line]) {
+          if (!seenLine[entry.line || 0]) {
             annotation.firstOnLine = true
-            seenLine[entry.line] = true
+            seenLine[entry.line || 0] = true
           }
         }
 
@@ -191,8 +224,10 @@ export function buildLogEntryAnnotations(entries, fileTreeData, rootDocId) {
   return logEntryAnnotations
 }
 
-export const buildRuleCounts = (entries = []) => {
-  const counts = {}
+export const buildRuleCounts = (
+  entries: LogEntry[] = []
+): Record<string, number> => {
+  const counts: Record<string, number> = {}
   for (const entry of entries) {
     const key = `${entry.level}_${entry.ruleId}`
     counts[key] = counts[key] ? counts[key] + 1 : 1
@@ -200,16 +235,17 @@ export const buildRuleCounts = (entries = []) => {
   return counts
 }
 
-export const buildRuleDeltas = (ruleCounts, previousRuleCounts) => {
-  const counts = {}
+export const buildRuleDeltas = (
+  ruleCounts: Record<string, number>,
+  previousRuleCounts: Record<string, number>
+): Record<string, number> => {
+  const counts: Record<string, number> = {}
 
-  // keys that are defined in the current log entries
   for (const [key, value] of Object.entries(ruleCounts)) {
     const previousValue = previousRuleCounts[key] ?? 0
     counts[`delta_${key}`] = value - previousValue
   }
 
-  // keys that are no longer defined in the current log entries
   for (const [key, value] of Object.entries(previousRuleCounts)) {
     if (!(key in ruleCounts)) {
       counts[key] = 0
@@ -220,7 +256,7 @@ export const buildRuleDeltas = (ruleCounts, previousRuleCounts) => {
   return counts
 }
 
-function buildURL(file, pdfDownloadDomain) {
+function buildURL(file: PDFFile, pdfDownloadDomain?: string): string {
   if (file.build && pdfDownloadDomain) {
     // Downloads from the compiles domain must include a build id.
     // The build id is used implicitly for access control.
@@ -230,13 +266,15 @@ function buildURL(file, pdfDownloadDomain) {
   return `${window.origin}${file.url}`
 }
 
-function normalizeFilePath(path, rootDocDirname) {
+function normalizeFilePath(
+  path: string,
+  rootDocDirname?: string | null
+): string {
   path = path.replace(/\/\//g, '/')
   path = path.replace(
     /^.*\/compiles\/[0-9a-f]{24}(-[0-9a-f]{24})?\/(\.\/)?/,
     ''
   )
-
   path = path.replace(/^\/compile\//, '')
 
   if (rootDocDirname) {
@@ -246,11 +284,15 @@ function normalizeFilePath(path, rootDocDirname) {
   return path
 }
 
-function isTransientWarning(warning) {
-  return TRANSIENT_WARNING_REGEX.test(warning.message)
+function isTransientWarning(warning: LatexLogEntry): boolean {
+  return TRANSIENT_WARNING_REGEX.test(warning.message || '')
 }
 
-async function fetchFileWithSizeLimit(url, signal, maxSize) {
+async function fetchFileWithSizeLimit(
+  url: string,
+  signal: AbortSignal,
+  maxSize: number
+): Promise<string> {
   let result = ''
   try {
     const abortController = new AbortController()
@@ -267,11 +309,13 @@ async function fetchFileWithSizeLimit(url, signal, maxSize) {
       throw new Error('Failed to fetch log file')
     }
 
-    const reader = response.body.pipeThrough(new TextDecoderStream())
-    for await (const chunk of reader) {
-      result += chunk
-      if (result.length > maxSize) {
-        abortController.abort()
+    const reader = response.body?.pipeThrough(new TextDecoderStream())
+    if (reader) {
+      for await (const chunk of reader) {
+        result += chunk
+        if (result.length > maxSize) {
+          abortController.abort()
+        }
       }
     }
   } catch (e) {

+ 1 - 0
services/web/frontend/js/features/pdf-preview/util/types.ts

@@ -14,6 +14,7 @@ export type LogEntry = {
   type?: string
   messageComponent?: React.ReactNode
   contentDetails?: string[]
+  command?: string
 }
 
 export type ErrorLevel =

+ 1 - 1
services/web/frontend/js/ide/log-parser/bib-log-parser.ts

@@ -64,7 +64,7 @@ const parserReducer = function (maxErrors: number | null) {
   }
 }
 
-type BibLogEntry = {
+export type BibLogEntry = {
   file: string
   level: string
   message: string

+ 1 - 1
services/web/frontend/js/ide/log-parser/latex-log-parser.ts

@@ -19,7 +19,7 @@ export type LatexParserOptions = {
   ignoreDuplicates?: boolean
 }
 
-type LatexLogEntry = {
+export type LatexLogEntry = {
   line: string | number | null
   file: string | undefined
   level: 'error' | 'warning' | 'typesetting'

+ 4 - 4
services/web/frontend/js/shared/context/local-compile-context.tsx

@@ -25,7 +25,7 @@ import {
   buildRuleDeltas,
   handleLogFiles,
   handleOutputFiles,
-} from '../../features/pdf-preview/util/output-files'
+} from '@/features/pdf-preview/util/output-files'
 import { useProjectContext } from './project-context'
 import { useEditorContext } from './editor-context'
 import { buildFileList } from '../../features/pdf-preview/util/file-list'
@@ -81,7 +81,7 @@ export type CompileContext = {
   logEntryAnnotations?: Record<string, Annotation[]>
   outputFilesArchive?: string
   pdfDownloadUrl?: string
-  pdfFile?: PdfFile
+  pdfFile?: PdfFile | null
   pdfUrl?: string
   pdfViewer?: string
   position?: PdfScrollPosition
@@ -167,7 +167,7 @@ export const LocalCompileProvider: FC<React.PropsWithChildren> = ({
   const { pdfViewer, syntaxValidation } = userSettings
 
   // low level details for metrics
-  const [pdfFile, setPdfFile] = useState<PdfFile | undefined>()
+  const [pdfFile, setPdfFile] = useState<PdfFile | null | undefined>()
 
   // the project is considered to be "uncompiled" if a doc has changed, or finished saving, since the last compile started.
   const [uncompiled, setUncompiled] = useState(false)
@@ -296,7 +296,7 @@ export const LocalCompileProvider: FC<React.PropsWithChildren> = ({
   }, [compiling])
 
   const _buildLogEntryAnnotations = useCallback(
-    (entries: any) =>
+    (entries: LogEntry[]) =>
       buildLogEntryAnnotations(entries, fileTreeData, lastCompileRootDocId),
     [fileTreeData, lastCompileRootDocId]
   )

+ 1 - 1
services/web/types/annotation.ts

@@ -6,6 +6,6 @@ export type Annotation = {
   ruleId?: string
   id: string
   entryIndex: number
-  firstOnLine: boolean
+  firstOnLine?: boolean
   command?: string
 }