Skip to content

⬅️ Back to Table of Contents

📄 useProgramFromProjectService

📊 Analysis Summary

Metric Count
🔧 Functions 9
📦 Imports 13
📊 Variables & Constants 2

📚 Table of Contents

🛠️ File Location:

📂 packages/typescript-estree/src/useProgramFromProjectService.ts

📦 Imports

Name Source
ProjectServiceAndMetadata @typescript-eslint/project-service
debug debug
minimatch minimatch
path node:path
util node:util
ASTAndDefiniteProgram ./create-program/shared
ASTAndNoProgram ./create-program/shared
ASTAndProgram ./create-program/shared
MutableParseSettings ./parseSettings
createProjectProgram ./create-program/createProjectProgram
createNoProgram ./create-program/createSourceFile
DEFAULT_EXTRA_FILE_EXTENSIONS ./create-program/shared
DEFAULT_PROJECT_FILES_ERROR_EXPLANATION ./create-program/validateDefaultProjectForFilesGlob

Variables & Constants

Name Type Kind Value Exported
RELOAD_THROTTLE_MS 250 const 250
serviceFileExtensions WeakMap<ts.server.ProjectService, str... const new WeakMap<ts.server.ProjectService, string[]>()

Functions

useProgramFromProjectService(…): ASTAndProgram | undefined

Parameters:

  • serviceAndSettings ProjectServiceAndMetadata
  • parseSettings Readonly<MutableParseSettings>
  • hasFullTypeInformation boolean
  • defaultProjectMatchedFiles Set<string>

Returns: ASTAndProgram | undefined

Code
export function useProgramFromProjectService(
  serviceAndSettings: ProjectServiceAndMetadata,
  parseSettings: Readonly<MutableParseSettings>,
  hasFullTypeInformation: boolean,
  defaultProjectMatchedFiles: Set<string>,
): ASTAndProgram | undefined;

updateExtraFileExtensions(service: ts.server.ProjectService, extraFileExtensions: string[]): void

Parameters:

  • service ts.server.ProjectService
  • extraFileExtensions string[]

Returns: void

Calls:

  • serviceFileExtensions.get
  • util.isDeepStrictEqual
  • log
  • service.setHostConfiguration
  • extraFileExtensions.map
  • serviceFileExtensions.set
Code
(
  service: ts.server.ProjectService,
  extraFileExtensions: string[],
): void => {
  const currentServiceFileExtensions = serviceFileExtensions.get(service) ?? [];
  if (
    !util.isDeepStrictEqual(currentServiceFileExtensions, extraFileExtensions)
  ) {
    log(
      'Updating extra file extensions: before=%s: after=%s',
      currentServiceFileExtensions,
      extraFileExtensions,
    );
    service.setHostConfiguration({
      extraFileExtensions: extraFileExtensions.map(extension => ({
        extension,
        isMixedContent: false,
        scriptKind: ts.ScriptKind.Deferred,
      })),
    });
    serviceFileExtensions.set(service, extraFileExtensions);
    log('Extra file extensions updated: %o', extraFileExtensions);
  }
}

openClientFileFromProjectService(…): ts.server.OpenConfiguredProjectResult

Parameters:

  • defaultProjectMatchedFiles Set<string>
  • isDefaultProjectAllowed boolean
  • filePathAbsolute string
  • parseSettings Readonly<MutableParseSettings>
  • serviceAndSettings ProjectServiceAndMetadata

Returns: ts.server.OpenConfiguredProjectResult

Calls:

  • openClientFileAndMaybeReload
  • log
  • path.extname
  • DEFAULT_EXTRA_FILE_EXTENSIONS.has
  • extraFileExtensions.includes
  • path.relative
  • [ baseMessage,allowDefaultProject is set to ${JSON.stringify(allowDefaultProject)}, which does not match '${relativeFilePath}'., ].join
  • JSON.stringify
  • defaultProjectMatchedFiles.add
  • [...defaultProjectMatchedFiles].slice
  • filesToPrint.map(file =>- ${file}).join
  • serviceAndSettings.service.openClientFile
  • openClientFile
  • performance.now
  • serviceAndSettings.service.reloadProjects

Internal Comments:

// No a configFileName indicates this file wasn't included in a TSConfig.
// That means it must get its type information from the default project.
/* scriptKind */
// If no project included the file and we're not in single-run mode,
// we might be running in an editor with outdated file info.
// We can try refreshing the project service - debounced for performance.

Code
function openClientFileFromProjectService(
  defaultProjectMatchedFiles: Set<string>,
  isDefaultProjectAllowed: boolean,
  filePathAbsolute: string,
  parseSettings: Readonly<MutableParseSettings>,
  serviceAndSettings: ProjectServiceAndMetadata,
): ts.server.OpenConfiguredProjectResult {
  const opened = openClientFileAndMaybeReload();

  log('Result from attempting to open client file: %o', opened);

  log(
    'Default project allowed path: %s, based on config file: %s',
    isDefaultProjectAllowed,
    opened.configFileName,
  );

  if (opened.configFileName) {
    if (isDefaultProjectAllowed) {
      throw new Error(
        `${parseSettings.filePath} was included by allowDefaultProject but also was found in the project service. Consider removing it from allowDefaultProject.`,
      );
    }
  } else {
    const wasNotFound = `${parseSettings.filePath} was not found by the project service`;

    const fileExtension = path.extname(parseSettings.filePath);
    const extraFileExtensions = parseSettings.extraFileExtensions;
    if (
      !DEFAULT_EXTRA_FILE_EXTENSIONS.has(fileExtension) &&
      !extraFileExtensions.includes(fileExtension)
    ) {
      const nonStandardExt = `${wasNotFound} because the extension for the file (\`${fileExtension}\`) is non-standard`;
      if (extraFileExtensions.length > 0) {
        throw new Error(
          `${nonStandardExt}. It should be added to your existing \`parserOptions.extraFileExtensions\`.`,
        );
      } else {
        throw new Error(
          `${nonStandardExt}. You should add \`parserOptions.extraFileExtensions\` to your config.`,
        );
      }
    }

    if (!isDefaultProjectAllowed) {
      const baseMessage = `${wasNotFound}. Consider either including it in the tsconfig.json or including it in allowDefaultProject.`;
      const allowDefaultProject =
        parseSettings.projectService?.allowDefaultProject;

      if (!allowDefaultProject) {
        throw new Error(baseMessage);
      }

      const relativeFilePath = path.relative(
        parseSettings.tsconfigRootDir,
        filePathAbsolute,
      );

      throw new Error(
        [
          baseMessage,
          `allowDefaultProject is set to ${JSON.stringify(allowDefaultProject)}, which does not match '${relativeFilePath}'.`,
        ].join('\n'),
      );
    }
  }

  // No a configFileName indicates this file wasn't included in a TSConfig.
  // That means it must get its type information from the default project.
  if (!opened.configFileName) {
    defaultProjectMatchedFiles.add(filePathAbsolute);
    if (
      defaultProjectMatchedFiles.size >
      serviceAndSettings.maximumDefaultProjectFileMatchCount
    ) {
      const filePrintLimit = 20;
      const filesToPrint = [...defaultProjectMatchedFiles].slice(
        0,
        filePrintLimit,
      );
      const truncatedFileCount =
        defaultProjectMatchedFiles.size - filesToPrint.length;

      throw new Error(
        `Too many files (>${serviceAndSettings.maximumDefaultProjectFileMatchCount}) have matched the default project.${DEFAULT_PROJECT_FILES_ERROR_EXPLANATION}
Matching files:
${filesToPrint.map(file => `- ${file}`).join('\n')}
${truncatedFileCount ? `...and ${truncatedFileCount} more files\n` : ''}
If you absolutely need more files included, set parserOptions.projectService.maximumDefaultProjectFileMatchCount_THIS_WILL_SLOW_DOWN_LINTING to a larger value.
`,
      );
    }
  }

  return opened;

  function openClientFile(): ts.server.OpenConfiguredProjectResult {
    return serviceAndSettings.service.openClientFile(
      filePathAbsolute,
      parseSettings.codeFullText,
      /* scriptKind */ undefined,
      parseSettings.tsconfigRootDir,
    );
  }

  function openClientFileAndMaybeReload(): ts.server.OpenConfiguredProjectResult {
    log('Opening project service client file at path: %s', filePathAbsolute);

    let opened = openClientFile();

    // If no project included the file and we're not in single-run mode,
    // we might be running in an editor with outdated file info.
    // We can try refreshing the project service - debounced for performance.
    if (
      !opened.configFileErrors &&
      !opened.configFileName &&
      !parseSettings.singleRun &&
      !isDefaultProjectAllowed &&
      performance.now() - serviceAndSettings.lastReloadTimestamp >
        RELOAD_THROTTLE_MS
    ) {
      log('No config file found; reloading project service and retrying.');
      serviceAndSettings.service.reloadProjects();
      opened = openClientFile();
      serviceAndSettings.lastReloadTimestamp = performance.now();
    }

    return opened;
  }
}

createNoProgramWithProjectService(filePathAbsolute: string, parseSettings: Readonly<MutableParseSettings>, service: ts.server.ProjectService): ASTAndNoProgram

Parameters:

  • filePathAbsolute string
  • parseSettings Readonly<MutableParseSettings>
  • service ts.server.ProjectService

Returns: ASTAndNoProgram

Calls:

  • log
  • service.getScriptInfo
  • service.openClientFile
  • createNoProgram (from ./create-program/createSourceFile)

Internal Comments:

// If the project service knows about this file, this informs if of changes.
// Doing so ensures that:
// - if the file is not part of a project, we don't waste time creating a program (fast non-type-aware linting)
// - otherwise, we refresh the file in the project service (moderately fast, since the project is already loaded)
/* scriptKind */

Code
function createNoProgramWithProjectService(
  filePathAbsolute: string,
  parseSettings: Readonly<MutableParseSettings>,
  service: ts.server.ProjectService,
): ASTAndNoProgram {
  log('No project service information available. Creating no program.');

  // If the project service knows about this file, this informs if of changes.
  // Doing so ensures that:
  // - if the file is not part of a project, we don't waste time creating a program (fast non-type-aware linting)
  // - otherwise, we refresh the file in the project service (moderately fast, since the project is already loaded)
  if (service.getScriptInfo(filePathAbsolute)) {
    log('Script info available. Opening client file in project service.');
    service.openClientFile(
      filePathAbsolute,
      parseSettings.codeFullText,
      /* scriptKind */ undefined,
      parseSettings.tsconfigRootDir,
    );
  }

  return createNoProgram(parseSettings);
}

retrieveASTAndProgramFor(filePathAbsolute: string, parseSettings: Readonly<MutableParseSettings>, serviceAndSettings: ProjectServiceAndMetadata): ASTAndDefiniteProgram | undefined

Parameters:

  • filePathAbsolute string
  • parseSettings Readonly<MutableParseSettings>
  • serviceAndSettings ProjectServiceAndMetadata

Returns: ASTAndDefiniteProgram | undefined

Calls:

  • log
  • serviceAndSettings.service.getScriptInfo
  • serviceAndSettings.service .getDefaultProjectForFile(scriptInfo!.fileName, true)! .getLanguageService(/*ensureSynchronized*/ true) .getProgram
  • createProjectProgram (from ./create-program/createProjectProgram)

Internal Comments:

/* eslint-disable @typescript-eslint/no-non-null-assertion */ (x2)
/* eslint-enable @typescript-eslint/no-non-null-assertion */

Code
function retrieveASTAndProgramFor(
  filePathAbsolute: string,
  parseSettings: Readonly<MutableParseSettings>,
  serviceAndSettings: ProjectServiceAndMetadata,
): ASTAndDefiniteProgram | undefined {
  log('Retrieving script info and then program for: %s', filePathAbsolute);

  const scriptInfo = serviceAndSettings.service.getScriptInfo(filePathAbsolute);
  /* eslint-disable @typescript-eslint/no-non-null-assertion */
  const program = serviceAndSettings.service
    .getDefaultProjectForFile(scriptInfo!.fileName, true)!
    .getLanguageService(/*ensureSynchronized*/ true)
    .getProgram();
  /* eslint-enable @typescript-eslint/no-non-null-assertion */

  if (!program) {
    log('Could not find project service program for: %s', filePathAbsolute);
    return undefined;
  }

  log('Found project service program for: %s', filePathAbsolute);

  return createProjectProgram(parseSettings, [program]);
}

absolutify(filePath: string, serviceAndSettings: ProjectServiceAndMetadata): string

Parameters:

  • filePath string
  • serviceAndSettings ProjectServiceAndMetadata

Returns: string

Calls:

  • path.isAbsolute
  • path.join
  • serviceAndSettings.service.host.getCurrentDirectory
Code
function absolutify(
  filePath: string,
  serviceAndSettings: ProjectServiceAndMetadata,
): string {
  return path.isAbsolute(filePath)
    ? filePath
    : path.join(
        serviceAndSettings.service.host.getCurrentDirectory(),
        filePath,
      );
}

filePathMatchedBy(filePath: string, allowDefaultProject: string[] | undefined): boolean

Parameters:

  • filePath string
  • allowDefaultProject string[] | undefined

Returns: boolean

Calls:

  • allowDefaultProject?.some
  • minimatch (from minimatch)
Code
function filePathMatchedBy(
  filePath: string,
  allowDefaultProject: string[] | undefined,
): boolean {
  return !!allowDefaultProject?.some(pattern =>
    minimatch(filePath, pattern, { dot: true }),
  );
}

Internal helpers

Declared inside another function in this file.

openClientFile(): ts.server.OpenConfiguredProjectResult

Returns: ts.server.OpenConfiguredProjectResult

Calls:

  • serviceAndSettings.service.openClientFile

Internal Comments:

/* scriptKind */

Code
function openClientFile(): ts.server.OpenConfiguredProjectResult {
    return serviceAndSettings.service.openClientFile(
      filePathAbsolute,
      parseSettings.codeFullText,
      /* scriptKind */ undefined,
      parseSettings.tsconfigRootDir,
    );
  }

openClientFileAndMaybeReload(): ts.server.OpenConfiguredProjectResult

Returns: ts.server.OpenConfiguredProjectResult

Calls:

  • log
  • openClientFile
  • performance.now
  • serviceAndSettings.service.reloadProjects

Internal Comments:

// If no project included the file and we're not in single-run mode,
// we might be running in an editor with outdated file info.
// We can try refreshing the project service - debounced for performance.

Code
function openClientFileAndMaybeReload(): ts.server.OpenConfiguredProjectResult {
    log('Opening project service client file at path: %s', filePathAbsolute);

    let opened = openClientFile();

    // If no project included the file and we're not in single-run mode,
    // we might be running in an editor with outdated file info.
    // We can try refreshing the project service - debounced for performance.
    if (
      !opened.configFileErrors &&
      !opened.configFileName &&
      !parseSettings.singleRun &&
      !isDefaultProjectAllowed &&
      performance.now() - serviceAndSettings.lastReloadTimestamp >
        RELOAD_THROTTLE_MS
    ) {
      log('No config file found; reloading project service and retrying.');
      serviceAndSettings.service.reloadProjects();
      opened = openClientFile();
      serviceAndSettings.lastReloadTimestamp = performance.now();
    }

    return opened;
  }

Generated by Syntax Scribe