<commit_before>import * as vscode from 'vscode'
import * as path from 'path'
import * as fs from 'fs'
import * as os from 'os'
import {ChildProcessWithoutNullStreams, spawn, SpawnOptionsWithoutStdio} from 'child_process'
import {EOL} from 'os'

import type {Extension} from '../main'

export class Linter {
    private readonly extension: Extension
    private linterTimeout?: NodeJS.Timer
    private readonly currentProcesses: {[linterId: string]: ChildProcessWithoutNullStreams} = {}

    constructor(extension: Extension) {
        this.extension = extension
    }

    get rcPath() {
        let rcPath: string
        // 0. root file folder
        const root = this.extension.manager.rootFile
        if (root) {
            rcPath = path.resolve(path.dirname(root), './.chktexrc')
        } else {
            return
        }
        if (fs.existsSync(rcPath)) {
            return rcPath
        }

        // 1. project root folder
        const ws = vscode.workspace.workspaceFolders
        if (ws && ws.length > 0) {
            rcPath = path.resolve(ws[0].uri.fsPath, './.chktexrc')
        }
        if (fs.existsSync(rcPath)) {
            return rcPath
        }
        return undefined
    }

    private globalRcPath(): string | undefined {
        const rcPathArray: string[] = []
        if (os.platform() === 'win32') {
            if (process.env.CHKTEXRC) {
                rcPathArray.push(path.join(process.env.CHKTEXRC, 'chktexrc'))
            }
            if (process.env.CHKTEX_HOME) {
                rcPathArray.push(path.join(process.env.CHKTEX_HOME, 'chktexrc'))
            }
            if (process.env.EMTEXDIR) {
                rcPathArray.push(path.join(process.env.EMTEXDIR, 'data', 'chktexrc'))
            }
        } else {
            if (process.env.HOME) {
                rcPathArray.push(path.join(process.env.HOME, '.chktexrc'))
            }
            if (process.env.LOGDIR) {
                rcPathArray.push(path.join(process.env.LOGDIR, '.chktexrc'))
            }
            if (process.env.CHKTEXRC) {
                rcPathArray.push(path.join(process.env.CHKTEXRC, '.chktexrc'))
            }
        }
        for (const rcPath of rcPathArray) {
            if (fs.existsSync(rcPath)) {
                return rcPath
            }
        }
        return
    }

    lintRootFileIfEnabled() {
        const configuration = vscode.workspace.getConfiguration('latex-workshop')
        if (configuration.get('chktex.enabled') as boolean) {
            this.lintRootFile()
        }
    }

    lintActiveFileIfEnabled() {
        const configuration = vscode.workspace.getConfiguration('latex-workshop')
        if ((configuration.get('chktex.enabled') as boolean) &&
            (configuration.get('chktex.run') as string) === 'onType') {
            this.lintActiveFile()
        }
    }

    lintActiveFileIfEnabledAfterInterval() {
        const configuration = vscode.workspace.getConfiguration('latex-workshop')
        if ((configuration.get('chktex.enabled') as boolean) &&
            (configuration.get('chktex.run') as string) === 'onType') {
            const interval = configuration.get('chktex.delay') as number
            if (this.linterTimeout) {
                clearTimeout(this.linterTimeout)
            }
            this.linterTimeout = setTimeout(() => { this.lintActiveFile() }, interval)
        }
    }

    async lintActiveFile() {
        if (!vscode.window.activeTextEditor || !vscode.window.activeTextEditor.document.getText()) {
            return
        }
        this.extension.logger.addLogMessage('Linter for active file started.')
        const filePath = vscode.window.activeTextEditor.document.fileName
        const content = vscode.window.activeTextEditor.document.getText()

        const configuration = vscode.workspace.getConfiguration('latex-workshop')
        const command = configuration.get('chktex.path') as string
        const args = [...(configuration.get('chktex.args.active') as string[])]
        if (!args.includes('-l')) {
            const rcPath = this.rcPath
            if (rcPath) {
                args.push('-l', rcPath)
            }
        }
        const requiredArgs = ['-I0', '-f%f:%l:%c:%d:%k:%n:%m\n']

        let stdout: string
        try {
            stdout = await this.processWrapper('active file', command, args.concat(requiredArgs).filter(arg => arg !== ''), {cwd: path.dirname(filePath)}, content)
        } catch (err) {
            if ('stdout' in err) {
                stdout = err.stdout
            } else {
                return
            }
        }
        // provide the original path to the active file as the second argument, so
        // we report this second path in the diagnostics instead of the temporary one.
        const tabSize = this.getChktexrcTabSize()
        this.extension.linterLogParser.parse(stdout, filePath, tabSize)
    }

    async lintRootFile() {
        this.extension.logger.addLogMessage('Linter for root file started.')
        if (this.extension.manager.rootFile === undefined) {
            this.extension.logger.addLogMessage('No root file found for linting.')
            return
        }

        const filePath = this.extension.manager.rootFile
        const configuration = vscode.workspace.getConfiguration('latex-workshop')
        const command = configuration.get('chktex.path') as string
        const args = [...(configuration.get('chktex.args.active') as string[])]
        if (!args.includes('-l')) {
            const rcPath = this.rcPath
            if (rcPath) {
                args.push('-l', rcPath)
            }
        }
        const requiredArgs = ['-f%f:%l:%c:%d:%k:%n:%m\n', filePath]

        let stdout: string
        try {
            stdout = await this.processWrapper('root file', command, args.concat(requiredArgs).filter(arg => arg !== ''), {cwd: path.dirname(this.extension.manager.rootFile)})
        } catch (err) {
            if ('stdout' in err) {
                stdout = err.stdout
            } else {
                return
            }
        }
        const tabSize = this.getChktexrcTabSize()
        this.extension.linterLogParser.parse(stdout, undefined, tabSize)
    }

    private getChktexrcTabSize(): number | undefined {
        const configuration = vscode.workspace.getConfiguration('latex-workshop')
        const args = configuration.get('chktex.args.active') as string[]
        let filePath: string | undefined
        if (args.includes('-l')) {
            const idx = args.indexOf('-l')
            if (idx > 0) {
                const rcpath = args[idx+1]
                if (fs.existsSync(rcpath)) {
                    filePath = rcpath
                }
            }
        } else {
            if (this.rcPath) {
                filePath = this.rcPath
            } else {
                filePath = this.globalRcPath()
            }
        }
        if (!filePath) {
            this.extension.logger.addLogMessage('The .chktexrc file not found.')
            return
        }
        const rcFile = fs.readFileSync(filePath).toString()
        const reg = /^\s*TabSize\s*=\s*(\d+)\s*$/m
        const match = reg.exec(rcFile)
        if (match) {
            return Number(match[1])
        }
        this.extension.logger.addLogMessage(`TabSize not found in the .chktexrc file: ${filePath}`)
        return<commit_msg>Minor fix.<commit_after>import * as vscode from 'vscode'
import * as path from 'path'
import * as fs from 'fs'
import * as os from 'os'
import {ChildProcessWithoutNullStreams, spawn, SpawnOptionsWithoutStdio} from 'child_process'
import {EOL} from 'os'

import type {Extension} from '../main'

export class Linter {
    private readonly extension: Extension
    private linterTimeout?: NodeJS.Timer
    private readonly currentProcesses: {[linterId: string]: ChildProcessWithoutNullStreams} = {}

    constructor(extension: Extension) {
        this.extension = extension
    }

    get rcPath() {
        let rcPath: string
        // 0. root file folder
        const root = this.extension.manager.rootFile
        if (root) {
            rcPath = path.resolve(path.dirname(root), './.chktexrc')
        } else {
            return
        }
        if (fs.existsSync(rcPath)) {
            return rcPath
        }

        // 1. project root folder
        const ws = vscode.workspace.workspaceFolders
        if (ws && ws.length > 0) {
            rcPath = path.resolve(ws[0].uri.fsPath, './.chktexrc')
        }
        if (fs.existsSync(rcPath)) {
            return rcPath
        }
        return undefined
    }

    private globalRcPath(): string | undefined {
        const rcPathArray: string[] = []
        if (os.platform() === 'win32') {
            if (process.env.CHKTEXRC) {
                rcPathArray.push(path.join(process.env.CHKTEXRC, 'chktexrc'))
            }
            if (process.env.CHKTEX_HOME) {
                rcPathArray.push(path.join(process.env.CHKTEX_HOME, 'chktexrc'))
            }
            if (process.env.EMTEXDIR) {
                rcPathArray.push(path.join(process.env.EMTEXDIR, 'data', 'chktexrc'))
            }
        } else {
            if (process.env.HOME) {
                rcPathArray.push(path.join(process.env.HOME, '.chktexrc'))
            }
            if (process.env.LOGDIR) {
                rcPathArray.push(path.join(process.env.LOGDIR, '.chktexrc'))
            }
            if (process.env.CHKTEXRC) {
                rcPathArray.push(path.join(process.env.CHKTEXRC, '.chktexrc'))
            }
        }
        for (const rcPath of rcPathArray) {
            if (fs.existsSync(rcPath)) {
                return rcPath
            }
        }
        return
    }

    lintRootFileIfEnabled() {
        const configuration = vscode.workspace.getConfiguration('latex-workshop')
        if (configuration.get('chktex.enabled') as boolean) {
            this.lintRootFile()
        }
    }

    lintActiveFileIfEnabled() {
        const configuration = vscode.workspace.getConfiguration('latex-workshop')
        if ((configuration.get('chktex.enabled') as boolean) &&
            (configuration.get('chktex.run') as string) === 'onType') {
            this.lintActiveFile()
        }
    }

    lintActiveFileIfEnabledAfterInterval() {
        const configuration = vscode.workspace.getConfiguration('latex-workshop')
        if ((configuration.get('chktex.enabled') as boolean) &&
            (configuration.get('chktex.run') as string) === 'onType') {
            const interval = configuration.get('chktex.delay') as number
            if (this.linterTimeout) {
                clearTimeout(this.linterTimeout)
            }
            this.linterTimeout = setTimeout(() => { this.lintActiveFile() }, interval)
        }
    }

    async lintActiveFile() {
        if (!vscode.window.activeTextEditor || !vscode.window.activeTextEditor.document.getText()) {
            return
        }
        this.extension.logger.addLogMessage('Linter for active file started.')
        const filePath = vscode.window.activeTextEditor.document.fileName
        const content = vscode.window.activeTextEditor.document.getText()

        const configuration = vscode.workspace.getConfiguration('latex-workshop')
        const command = configuration.get('chktex.path') as string
        const args = [...(configuration.get('chktex.args.active') as string[])]
        if (!args.includes('-l')) {
            const rcPath = this.rcPath
            if (rcPath) {
                args.push('-l', rcPath)
            }
        }
        const requiredArgs = ['-I0', '-f%f:%l:%c:%d:%k:%n:%m\n']

        let stdout: string
        try {
            stdout = await this.processWrapper('active file', command, args.concat(requiredArgs).filter(arg => arg !== ''), {cwd: path.dirname(filePath)}, content)
        } catch (err) {
            if ('stdout' in err) {
                stdout = err.stdout
            } else {
                return
            }
        }
        // provide the original path to the active file as the second argument, so
        // we report this second path in the diagnostics instead of the temporary one.
        const tabSize = this.getChktexrcTabSize()
        this.extension.linterLogParser.parse(stdout, filePath, tabSize)
    }

    async lintRootFile() {
        this.extension.logger.addLogMessage('Linter for root file started.')
        if (this.extension.manager.rootFile === undefined) {
            this.extension.logger.addLogMessage('No root file found for linting.')
            return
        }

        const filePath = this.extension.manager.rootFile
        const configuration = vscode.workspace.getConfiguration('latex-workshop')
        const command = configuration.get('chktex.path') as string
        const args = [...(configuration.get('chktex.args.active') as string[])]
        if (!args.includes('-l')) {
            const rcPath = this.rcPath
            if (rcPath) {
                args.push('-l', rcPath)
            }
        }
        const requiredArgs = ['-f%f:%l:%c:%d:%k:%n:%m\n', filePath]

        let stdout: string
        try {
            stdout = await this.processWrapper('root file', command, args.concat(requiredArgs).filter(arg => arg !== ''), {cwd: path.dirname(this.extension.manager.rootFile)})
        } catch (err) {
            if ('stdout' in err) {
                stdout = err.stdout
            } else {
                return
            }
        }
        const tabSize = this.getChktexrcTabSize()
        this.extension.linterLogParser.parse(stdout, undefined, tabSize)
    }

    private getChktexrcTabSize(): number | undefined {
        const configuration = vscode.workspace.getConfiguration('latex-workshop')
        const args = configuration.get('chktex.args.active') as string[]
        let filePath: string | undefined
        if (args.includes('-l')) {
            const idx = args.indexOf('-l')
            if (idx >= 0) {
                const rcpath = args[idx+1]
                if (fs.existsSync(rcpath)) {
                    filePath = rcpath
                }
            }
        } else {
            if (this.rcPath) {
                filePath = this.rcPath
            } else {
                filePath = this.globalRcPath()
            }
        }
        if (!filePath) {
            this.extension.logger.addLogMessage('The .chktexrc file not found.')
            return
        }
        const rcFile = fs.readFileSync(filePath).toString()
        const reg = /^\s*TabSize\s*=\s*(\d+)\s*$/m
        const match = reg.exec(rcFile)
        if (match) {
            return Number(match[1])
        }
        this.extension.logger.addLogMessage(`TabSize not found in the .chktexrc file: ${filePath}`)
        return
{'fraction_non_alphanumeric': 0.12158137015976171, 'fraction_numerical': 0.0013539128080151638, 'mean_word_length': 1.9029278836706622, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 13, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2817043', 'n_tokens_mistral': 4239, 'n_tokens_neox': 4174, 'n_words': 866}