<commit_before>import { promises as fs } from 'fs'
import chalk from 'next/dist/compiled/chalk'
import path from 'path'

import findUp from 'next/dist/compiled/find-up'
import semver from 'next/dist/compiled/semver'
import * as CommentJson from 'next/dist/compiled/comment-json'

import { LintResult, formatResults } from './customFormatter'
import { writeDefaultConfig } from './writeDefaultConfig'
import { hasEslintConfiguration } from './hasEslintConfiguration'
import { writeOutputFile } from './writeOutputFile'

import { ESLINT_PROMPT_VALUES } from '../constants'
import { existsSync, findPagesDir } from '../find-pages-dir'
import { installDependencies } from '../install-dependencies'
import { hasNecessaryDependencies } from '../has-necessary-dependencies'

import * as Log from '../../build/output/log'
import { EventLintCheckCompleted } from '../../telemetry/events/build'
import isError, { getProperError } from '../is-error'
import { getPkgManager } from '../helpers/get-pkg-manager'

type Config = {
  plugins: string[]
  rules: { [key: string]: Array<number | string> }
}

// 0 is off, 1 is warn, 2 is error. See https://eslint.org/docs/user-guide/configuring/rules#configuring-rules
const VALID_SEVERITY = ['off', 'warn', 'error'] as const
type Severity = typeof VALID_SEVERITY[number]

function isValidSeverity(severity: string): severity is Severity {
  return VALID_SEVERITY.includes(severity as Severity)
}

const requiredPackages = [
  { file: 'eslint', pkg: 'eslint', exportsRestrict: false },
  {
    file: 'eslint-config-next',
    pkg: 'eslint-config-next',
    exportsRestrict: false,
  },
]

async function cliPrompt() {
  console.log(
    chalk.bold(
      `${chalk.cyan(
        '?'
      )} How would you like to configure ESLint? https://nextjs.org/docs/basic-features/eslint`
    )
  )

  try {
    const cliSelect = (
      await Promise.resolve(require('next/dist/compiled/cli-select'))
    ).default
    const { value } = await cliSelect({
      values: ESLINT_PROMPT_VALUES,
      valueRenderer: (
        {
          title,
          recommended,
        }: { title: string; recommended?: boolean; config: any },
        selected: boolean
      ) => {
        const name = selected ? chalk.bold.underline.cyan(title) : title
        return name + (recommended ? chalk.bold.yellow(' (recommended)') : '')
      },
      selected: chalk.cyan('❯ '),
      unselected: '  ',
    })

    return { config: value?.config }
  } catch {
    return { config: null }
  }
}

async function lint(
  baseDir: string,
  lintDirs: string[],
  eslintrcFile: string | null,
  pkgJsonPath: string | null,
  hasAppDir: boolean,
  {
    lintDuringBuild = false,
    eslintOptions = null,
    reportErrorsOnly = false,
    maxWarnings = -1,
    formatter = null,
    outputFile = null,
  }: {
    lintDuringBuild: boolean
    eslintOptions: any
    reportErrorsOnly: boolean
    maxWarnings: number
    formatter: string | null
    outputFile: string | null
  }
): Promise<
  | string
  | null
  | {
      output: string | null
      isError: boolean
      eventInfo: EventLintCheckCompleted
    }
> {
  try {
    // Load ESLint after we're sure it exists:
    const deps = await hasNecessaryDependencies(baseDir, requiredPackages)
    const packageManager = getPkgManager(baseDir)

    if (deps.missing.some((dep) => dep.pkg === 'eslint')) {
      Log.error(
        `ESLint must be installed${
          lintDuringBuild ? ' in order to run during builds:' : ':'
        } ${chalk.bold.cyan(
          (packageManager === 'yarn'
            ? 'yarn add --dev'
            : packageManager === 'pnpm'
            ? 'pnpm install --save-dev'
            : 'npm install --save-dev') + ' eslint'
        )}`
      )
      return null
    }

    const mod = await Promise.resolve(require(deps.resolved.get('eslint')!))

    const { ESLint } = mod
    let eslintVersion = ESLint?.version ?? mod?.CLIEngine?.version

    if (!eslintVersion || semver.lt(eslintVersion, '7.0.0')) {
      return `${chalk.red(
        'error'
      )} - Your project has an older version of ESLint installed${
        eslintVersion ? ' (' + eslintVersion + ')' : ''
      }. Please upgrade to ESLint version 7 or above`
    }

    let options: any = {
      useEslintrc: true,
      baseConfig: {},
      errorOnUnmatchedPattern: false,
      extensions: ['.js', '.jsx', '.ts', '.tsx'],
      cache: true,
      ...eslintOptions,
    }

    let eslint = new ESLint(options)

    let nextEslintPluginIsEnabled = false
    const nextRulesEnabled = new Map<string, Severity>()
    const pagesDirRules = ['@next/next/no-html-link-for-pages']

    for (const configFile of [eslintrcFile, pkgJsonPath]) {
      if (!configFile) continue

      const completeConfig: Config = await eslint.calculateConfigForFile(
        configFile
      )

      if (completeConfig.plugins?.includes('@next/next')) {
        nextEslintPluginIsEnabled = true
        for (const [name, [severity]] of Object.entries(completeConfig.rules)) {
          if (!name.startsWith('@next/next/')) {
            continue
          }
          if (
            typeof severity === 'number' &&
            severity >= 0 &&
            severity < VALID_SEVERITY.length
          ) {
            nextRulesEnabled.set(name, VALID_SEVERITY[severity])
          } else if (
            typeof severity === 'string' &&
            isValidSeverity(severity)
          ) {
            nextRulesEnabled.set(name, severity)
          }
        }
        break
      }
    }

    const pagesDir = findPagesDir(baseDir, hasAppDir).pages

    if (nextEslintPluginIsEnabled) {
      let updatedPagesDir = false

      for (const rule of pagesDirRules) {
        if (
          !options.baseConfig!.rules?.[rule] &&
          !options.baseConfig!.rules?.[
            rule.replace('@next/next', '@next/babel-plugin-next')
          ]
        ) {
          if (!options.baseConfig!.rules) {
            options.baseConfig!.rules = {}
          }
          options.baseConfig!.rules[rule] = [1, pagesDir]
          updatedPagesDir = true
        }
      }
<commit_msg>Skip pagesDirRules when pagesDir is not present (#41075)<commit_after>import { promises as fs } from 'fs'
import chalk from 'next/dist/compiled/chalk'
import path from 'path'

import findUp from 'next/dist/compiled/find-up'
import semver from 'next/dist/compiled/semver'
import * as CommentJson from 'next/dist/compiled/comment-json'

import { LintResult, formatResults } from './customFormatter'
import { writeDefaultConfig } from './writeDefaultConfig'
import { hasEslintConfiguration } from './hasEslintConfiguration'
import { writeOutputFile } from './writeOutputFile'

import { ESLINT_PROMPT_VALUES } from '../constants'
import { existsSync, findPagesDir } from '../find-pages-dir'
import { installDependencies } from '../install-dependencies'
import { hasNecessaryDependencies } from '../has-necessary-dependencies'

import * as Log from '../../build/output/log'
import { EventLintCheckCompleted } from '../../telemetry/events/build'
import isError, { getProperError } from '../is-error'
import { getPkgManager } from '../helpers/get-pkg-manager'

type Config = {
  plugins: string[]
  rules: { [key: string]: Array<number | string> }
}

// 0 is off, 1 is warn, 2 is error. See https://eslint.org/docs/user-guide/configuring/rules#configuring-rules
const VALID_SEVERITY = ['off', 'warn', 'error'] as const
type Severity = typeof VALID_SEVERITY[number]

function isValidSeverity(severity: string): severity is Severity {
  return VALID_SEVERITY.includes(severity as Severity)
}

const requiredPackages = [
  { file: 'eslint', pkg: 'eslint', exportsRestrict: false },
  {
    file: 'eslint-config-next',
    pkg: 'eslint-config-next',
    exportsRestrict: false,
  },
]

async function cliPrompt() {
  console.log(
    chalk.bold(
      `${chalk.cyan(
        '?'
      )} How would you like to configure ESLint? https://nextjs.org/docs/basic-features/eslint`
    )
  )

  try {
    const cliSelect = (
      await Promise.resolve(require('next/dist/compiled/cli-select'))
    ).default
    const { value } = await cliSelect({
      values: ESLINT_PROMPT_VALUES,
      valueRenderer: (
        {
          title,
          recommended,
        }: { title: string; recommended?: boolean; config: any },
        selected: boolean
      ) => {
        const name = selected ? chalk.bold.underline.cyan(title) : title
        return name + (recommended ? chalk.bold.yellow(' (recommended)') : '')
      },
      selected: chalk.cyan('❯ '),
      unselected: '  ',
    })

    return { config: value?.config }
  } catch {
    return { config: null }
  }
}

async function lint(
  baseDir: string,
  lintDirs: string[],
  eslintrcFile: string | null,
  pkgJsonPath: string | null,
  hasAppDir: boolean,
  {
    lintDuringBuild = false,
    eslintOptions = null,
    reportErrorsOnly = false,
    maxWarnings = -1,
    formatter = null,
    outputFile = null,
  }: {
    lintDuringBuild: boolean
    eslintOptions: any
    reportErrorsOnly: boolean
    maxWarnings: number
    formatter: string | null
    outputFile: string | null
  }
): Promise<
  | string
  | null
  | {
      output: string | null
      isError: boolean
      eventInfo: EventLintCheckCompleted
    }
> {
  try {
    // Load ESLint after we're sure it exists:
    const deps = await hasNecessaryDependencies(baseDir, requiredPackages)
    const packageManager = getPkgManager(baseDir)

    if (deps.missing.some((dep) => dep.pkg === 'eslint')) {
      Log.error(
        `ESLint must be installed${
          lintDuringBuild ? ' in order to run during builds:' : ':'
        } ${chalk.bold.cyan(
          (packageManager === 'yarn'
            ? 'yarn add --dev'
            : packageManager === 'pnpm'
            ? 'pnpm install --save-dev'
            : 'npm install --save-dev') + ' eslint'
        )}`
      )
      return null
    }

    const mod = await Promise.resolve(require(deps.resolved.get('eslint')!))

    const { ESLint } = mod
    let eslintVersion = ESLint?.version ?? mod?.CLIEngine?.version

    if (!eslintVersion || semver.lt(eslintVersion, '7.0.0')) {
      return `${chalk.red(
        'error'
      )} - Your project has an older version of ESLint installed${
        eslintVersion ? ' (' + eslintVersion + ')' : ''
      }. Please upgrade to ESLint version 7 or above`
    }

    let options: any = {
      useEslintrc: true,
      baseConfig: {},
      errorOnUnmatchedPattern: false,
      extensions: ['.js', '.jsx', '.ts', '.tsx'],
      cache: true,
      ...eslintOptions,
    }

    let eslint = new ESLint(options)

    let nextEslintPluginIsEnabled = false
    const nextRulesEnabled = new Map<string, Severity>()

    for (const configFile of [eslintrcFile, pkgJsonPath]) {
      if (!configFile) continue

      const completeConfig: Config = await eslint.calculateConfigForFile(
        configFile
      )

      if (completeConfig.plugins?.includes('@next/next')) {
        nextEslintPluginIsEnabled = true
        for (const [name, [severity]] of Object.entries(completeConfig.rules)) {
          if (!name.startsWith('@next/next/')) {
            continue
          }
          if (
            typeof severity === 'number' &&
            severity >= 0 &&
            severity < VALID_SEVERITY.length
          ) {
            nextRulesEnabled.set(name, VALID_SEVERITY[severity])
          } else if (
            typeof severity === 'string' &&
            isValidSeverity(severity)
          ) {
            nextRulesEnabled.set(name, severity)
          }
        }
        break
      }
    }

    const pagesDir = findPagesDir(baseDir, hasAppDir).pages
    const pagesDirRules = pagesDir ? ['@next/next/no-html-link-for-pages'] : []

    if (nextEslintPluginIsEnabled) {
      let updatedPagesDir = false

      for (const rule of pagesDirRules) {
        if (
          !options.baseConfig!.rules?.[rule] &&
          !options.baseConfig!.rules?.[
            rule.replace('@next/next', '@next/babel-plugin-next')
          ]
        ) {
          if (!options.baseConfig!.rules) {
            options.baseConfig!.rules = {}
          }
          options.baseConfig!.rules[rule] = [1, pagesDir]
          updatedPagesDir = true
        }
      }

{'fraction_non_alphanumeric': 0.12757805494415914, 'fraction_numerical': 0.002037988098149507, 'mean_word_length': 2.4931662870159452, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 15, 'https://': 4, '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': '3940670', 'n_tokens_mistral': 3837, 'n_tokens_neox': 3697, 'n_words': 874}