<commit_before>import arrayUnique from 'lodash/uniq'
import { Directory, Range, Point } from 'atom'
import type { TextEditor } from 'atom'
import type { Linter, Message, MessageSolution } from './types'

export const $version = '__$sb_linter_version'
export const $activated = '__$sb_linter_activated'
export const $requestLatest = '__$sb_linter_request_latest'
export const $requestLastReceived = '__$sb_linter_request_last_received'

export function shouldTriggerLinter(linter: Linter, wasTriggeredOnChange: boolean, scopes: Array<string>): boolean {
  if (wasTriggeredOnChange && !linter.lintsOnChange) {
    return false
  }
  return scopes.some(function (scope) {
    return linter.grammarScopes.includes(scope)
  })
}

export function getEditorCursorScopes(textEditor: TextEditor): Array<string> {
  return arrayUnique(
    textEditor.getCursors().reduce((scopes, cursor) => scopes.concat(cursor.getScopeDescriptor().getScopesArray()), ['*']),
  )
}

let minimatch: typeof import('minimatch')
export async function isPathIgnored(
  filePath: string | null | undefined,
  ignoredGlob: string,
  ignoredVCS: boolean,
): Promise<boolean> {
  if (!filePath) {
    return true
  }

  if (ignoredVCS) {
    const directory = new Directory(filePath)
    const repository = await atom.project.repositoryForDirectory(directory)
    if (repository && repository.isPathIgnored(filePath)) {
      return true
    }
  }
  const normalizedFilePath = process.platform === 'win32' ? filePath.replace(/\\/g, '/') : filePath
  if (!minimatch) {
    minimatch = require('minimatch')
  }
  return minimatch(normalizedFilePath, ignoredGlob)
}

export function updateMessageKey(message: Message) {
  const { reference, location } = message
  message.key = [
    `$LINTER:${message.linterName}`,
    `$LOCATION:${location.file}$${location.position.start.row}$${location.position.start.column}$${location.position.end.row}$${location.position.end.column}`,
    reference
      ? `$REFERENCE:${reference.file}$${reference.position ? `${reference.position.row}$${reference.position.column}` : ''}`
      : '$REFERENCE:null',
    `$EXCERPT:${message.excerpt}`,
    `$SEVERITY:${message.severity}`,
    message.icon ? `$ICON:${message.icon}` : '$ICON:null',
    message.url ? `$URL:${message.url}` : '$URL:null',
    typeof message.description === 'string' ? `$DESCRIPTION:${message.description}` : '$DESCRIPTION:null',
  ].join('')
}

export function normalizeMessages(linterName: string, messages: Array<Message>) {
  for (let i = 0, { length } = messages; i < length; ++i) {
    const message = messages[i]
    const { reference } = message
    if (Array.isArray(message.location.position)) {
      message.location.position = Range.fromObject(message.location.position)
    }
    if (reference && Array.isArray(reference.position)) {
      reference.position = Point.fromObject(reference.position)
    }
    const solutions = message.solutions
    if (Array.isArray(solutions)) {
      for (let j = 0, _length = solutions.length, solution: MessageSolution; j < _length; j++) {
        solution = solutions[j]
        if (Array.isArray(solution.position)) {
          solution.position = Range.fromObject(solution.position)
        }
      }
    }
    message.version = 2
    if (!message.linterName) {
      message.linterName = linterName
    }
    updateMessageKey(message)
  }
}

// update the key of the given messages
export function updateKeys(messages: Array<Message>) {
  messages.forEach(m => {
    updateMessageKey(m)
  })
}

// create a map from keys to messages
export function createKeyMessageMap(messages: Array<Message>): Map<string, Message> {
  const keyMessageMap = new Map()
  for (let i = 0, { length } = messages; i < length; ++i) {
    const message = messages[i]
    keyMessageMap.set(message.key, message)
  }
  return keyMessageMap
}

interface FlaggedMessages {
  oldKept: Array<Message>
  oldRemoved: Array<Message>
  newAdded: Array<Message>
}<commit_msg>fix: test for Range using instanceof<commit_after>import arrayUnique from 'lodash/uniq'
import { Directory, Range, Point, PointCompatible, RangeCompatible } from 'atom'
import type { TextEditor } from 'atom'
import type { Linter, Message, MessageSolution } from './types'

export const $version = '__$sb_linter_version'
export const $activated = '__$sb_linter_activated'
export const $requestLatest = '__$sb_linter_request_latest'
export const $requestLastReceived = '__$sb_linter_request_last_received'

export function shouldTriggerLinter(linter: Linter, wasTriggeredOnChange: boolean, scopes: Array<string>): boolean {
  if (wasTriggeredOnChange && !linter.lintsOnChange) {
    return false
  }
  return scopes.some(function (scope) {
    return linter.grammarScopes.includes(scope)
  })
}

export function getEditorCursorScopes(textEditor: TextEditor): Array<string> {
  return arrayUnique(
    textEditor.getCursors().reduce((scopes, cursor) => scopes.concat(cursor.getScopeDescriptor().getScopesArray()), ['*']),
  )
}

let minimatch: typeof import('minimatch')
export async function isPathIgnored(
  filePath: string | null | undefined,
  ignoredGlob: string,
  ignoredVCS: boolean,
): Promise<boolean> {
  if (!filePath) {
    return true
  }

  if (ignoredVCS) {
    const directory = new Directory(filePath)
    const repository = await atom.project.repositoryForDirectory(directory)
    if (repository && repository.isPathIgnored(filePath)) {
      return true
    }
  }
  const normalizedFilePath = process.platform === 'win32' ? filePath.replace(/\\/g, '/') : filePath
  if (!minimatch) {
    minimatch = require('minimatch')
  }
  return minimatch(normalizedFilePath, ignoredGlob)
}

export function updateMessageKey(message: Message) {
  const { reference, location } = message
  message.key = [
    `$LINTER:${message.linterName}`,
    `$LOCATION:${location.file}$${location.position.start.row}$${location.position.start.column}$${location.position.end.row}$${location.position.end.column}`,
    reference
      ? `$REFERENCE:${reference.file}$${reference.position ? `${reference.position.row}$${reference.position.column}` : ''}`
      : '$REFERENCE:null',
    `$EXCERPT:${message.excerpt}`,
    `$SEVERITY:${message.severity}`,
    message.icon ? `$ICON:${message.icon}` : '$ICON:null',
    message.url ? `$URL:${message.url}` : '$URL:null',
    typeof message.description === 'string' ? `$DESCRIPTION:${message.description}` : '$DESCRIPTION:null',
  ].join('')
}

export function normalizeMessages(linterName: string, messages: Array<Message>) {
  for (let i = 0, { length } = messages; i < length; ++i) {
    const message = messages[i]
    const { reference, solutions } = message
    // convert RangeCompatible to Range and PointCompatible to Point
    // NOTE (this is not covered in the types, but done for backward compatibility)
    message.location.position = getRangeClass(message.location.position)
    if (reference !== undefined && reference.position !== undefined) {
      reference.position = getPointClass(reference.position)
    }
    if (Array.isArray(solutions)) {
      for (let j = 0, _length = solutions.length, solution: MessageSolution; j < _length; j++) {
        solution = solutions[j]
        solution.position = getRangeClass(solution.position)
      }
    }
    message.version = 2
    if (!message.linterName) {
      message.linterName = linterName
    }
    updateMessageKey(message)
  }
}

/* convert RangeCompatible to Range */
function getPointClass(point: Point | PointCompatible) {
  if (!(point instanceof Point)) {
    return Point.fromObject(point)
  }
  return point
}

/* convert RangeCompatible to Range */
function getRangeClass(range: Range | RangeCompatible) {
  if (!(range instanceof Range)) {
    return Range.fromObject(range)
  }
  return range
}

// update the key of the given messages
export function updateKeys(messages: Array<Message>) {
  messages.forEach(m => {
    updateMessageKey(m)
  })
}

// create a map from keys to messages
export function createKeyMessageMap(messages: Array<Message>): Map<string, Message> {
  const keyMessageMap = new Map()
  for (let i = 0, { length } = messages; i < length; ++i) {
    const message = messages[i]
    keyMessageMap.set(message.key, message)
  }
  return keyMessageMap
}

interface FlaggedMessages {
  oldKept: Array<Message>
  oldRemoved: Array<Message>
  newAdded: Array<Message>
}
{'fraction_non_alphanumeric': 0.1340341655716163, 'fraction_numerical': 0.0014335204873969656, 'mean_word_length': 4.373555840821566, 'pattern_counts': {'":': 0, '<': 29, '<?xml version=': 0, '>': 27, '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': '5850219', 'n_tokens_mistral': 2558, 'n_tokens_neox': 2548, 'n_words': 572}