Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(typescript-estree): allow providing code as a ts.SourceFile #5892

Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 4 additions & 4 deletions packages/parser/src/parser.ts
Expand Up @@ -14,7 +14,7 @@ import {
visitorKeys,
} from '@typescript-eslint/typescript-estree';
import debug from 'debug';
import type { CompilerOptions } from 'typescript';
import type * as ts from 'typescript';
import { ScriptTarget } from 'typescript';

const log = debug('typescript-eslint:parser:parser');
Expand All @@ -41,7 +41,7 @@ function validateBoolean(
}

const LIB_FILENAME_REGEX = /lib\.(.+)\.d\.[cm]?ts$/;
function getLib(compilerOptions: CompilerOptions): Lib[] {
function getLib(compilerOptions: ts.CompilerOptions): Lib[] {
if (compilerOptions.lib) {
return compilerOptions.lib.reduce((acc, lib) => {
const match = LIB_FILENAME_REGEX.exec(lib.toLowerCase());
Expand Down Expand Up @@ -76,14 +76,14 @@ function getLib(compilerOptions: CompilerOptions): Lib[] {
}

function parse(
code: string,
code: string | ts.SourceFile,
options?: ParserOptions,
): ParseForESLintResult['ast'] {
return parseForESLint(code, options).ast;
}

function parseForESLint(
code: string,
code: string | ts.SourceFile,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI @Quramy - is there anything else you'd need/want done to help with the use case described in #774?

cc @mhartington - you were showing me using Quramy's language service plugin. I'm betting this will speed it up ever so slightly (though I'd be surprised if file parsing was a major bottleneck). ✨

options?: ParserOptions | null,
): ParseForESLintResult {
if (!options || typeof options !== 'object') {
Expand Down
2 changes: 1 addition & 1 deletion packages/typescript-estree/src/ast-converter.ts
Expand Up @@ -63,7 +63,7 @@ export function astConverter(
* Optionally convert and include all comments in the AST
*/
if (parseSettings.comment) {
estree.comments = convertComments(ast, parseSettings.code);
estree.comments = convertComments(ast, parseSettings.codeFullText);
}

const astMaps = instance.getASTMaps();
Expand Down
Expand Up @@ -53,7 +53,7 @@ function createDefaultProgram(
const oldReadFile = compilerHost.readFile;
compilerHost.readFile = (fileName: string): string | undefined =>
path.normalize(fileName) === path.normalize(parseSettings.filePath)
? parseSettings.code
? parseSettings.codeFullText
: oldReadFile(fileName);

const program = ts.createProgram(
Expand Down
Expand Up @@ -43,7 +43,7 @@ function createIsolatedProgram(parseSettings: ParseSettings): ASTAndProgram {
getSourceFile(filename: string) {
return ts.createSourceFile(
filename,
parseSettings.code,
parseSettings.codeFullText,
ts.ScriptTarget.Latest,
/* setParentNodes */ true,
getScriptKind(parseSettings.filePath, parseSettings.jsx),
Expand Down
17 changes: 10 additions & 7 deletions packages/typescript-estree/src/create-program/createSourceFile.ts
Expand Up @@ -2,6 +2,7 @@ import debug from 'debug';
import * as ts from 'typescript';

import type { ParseSettings } from '../parseSettings';
import { isSourceFile } from '../source-files';
import { getScriptKind } from './getScriptKind';

const log = debug('typescript-eslint:typescript-estree:createSourceFile');
Expand All @@ -13,13 +14,15 @@ function createSourceFile(parseSettings: ParseSettings): ts.SourceFile {
parseSettings.filePath,
);

return ts.createSourceFile(
parseSettings.filePath,
parseSettings.code,
ts.ScriptTarget.Latest,
/* setParentNodes */ true,
getScriptKind(parseSettings.filePath, parseSettings.jsx),
);
return isSourceFile(parseSettings.code)
? parseSettings.code
: ts.createSourceFile(
parseSettings.filePath,
parseSettings.codeFullText,
ts.ScriptTarget.Latest,
/* setParentNodes */ true,
getScriptKind(parseSettings.filePath, parseSettings.jsx),
);
}

export { createSourceFile };
Expand Up @@ -4,6 +4,7 @@ import semver from 'semver';
import * as ts from 'typescript';

import type { ParseSettings } from '../parseSettings';
import { getCodeText } from '../source-files';
import type { CanonicalPath } from './shared';
import {
canonicalDirname,
Expand Down Expand Up @@ -90,7 +91,10 @@ function saveWatchCallback(
/**
* Holds information about the file currently being linted
*/
const currentLintOperationState: { code: string; filePath: CanonicalPath } = {
const currentLintOperationState: {
code: string | ts.SourceFile;
filePath: CanonicalPath;
} = {
code: '',
filePath: '' as CanonicalPath,
};
Expand Down Expand Up @@ -148,7 +152,7 @@ function getProgramsForProjects(parseSettings: ParseSettings): ts.Program[] {

// Update file version if necessary
const fileWatchCallbacks = fileWatchCallbackTrackingMap.get(filePath);
const codeHash = createHash(parseSettings.code);
const codeHash = createHash(getCodeText(parseSettings.code));
if (
parsedFilesSeenHash.get(filePath) !== codeHash &&
fileWatchCallbacks &&
Expand Down Expand Up @@ -281,7 +285,7 @@ function createWatchProgram(
const filePath = getCanonicalFileName(filePathIn);
const fileContent =
filePath === currentLintOperationState.filePath
? currentLintOperationState.code
? getCodeText(currentLintOperationState.code)
: oldReadFile(filePath, encoding);
if (fileContent !== undefined) {
parsedFilesSeenHash.set(filePath, createHash(fileContent));
Expand Down
@@ -1,13 +1,15 @@
import debug from 'debug';
import { sync as globSync } from 'globby';
import isGlob from 'is-glob';
import type * as ts from 'typescript';

import type { CanonicalPath } from '../create-program/shared';
import {
ensureAbsolutePath,
getCanonicalFileName,
} from '../create-program/shared';
import type { TSESTreeOptions } from '../parser-options';
import { isSourceFile } from '../source-files';
import type { MutableParseSettings } from './index';
import { inferSingleRun } from './inferSingleRun';
import { warnAboutTSVersion } from './warnAboutTSVersion';
Expand All @@ -17,15 +19,17 @@ const log = debug(
);

export function createParseSettings(
code: string,
code: string | ts.SourceFile,
options: Partial<TSESTreeOptions> = {},
): MutableParseSettings {
const codeFullText = enforceCodeString(code);
const tsconfigRootDir =
typeof options.tsconfigRootDir === 'string'
? options.tsconfigRootDir
: process.cwd();
const parseSettings: MutableParseSettings = {
code: enforceString(code),
code,
codeFullText,
comment: options.comment === true,
comments: [],
createDefaultProgram: options.createDefaultProgram === true,
Expand Down Expand Up @@ -125,12 +129,12 @@ export function createParseSettings(
/**
* Ensures source code is a string.
*/
function enforceString(code: unknown): string {
if (typeof code !== 'string') {
return String(code);
}

return code;
function enforceCodeString(code: unknown): string {
return isSourceFile(code)
? code.getFullText(code)
: typeof code === 'string'
? code
: String(code);
}

/**
Expand Down
9 changes: 7 additions & 2 deletions packages/typescript-estree/src/parseSettings/index.ts
Expand Up @@ -10,9 +10,14 @@ type DebugModule = 'typescript-eslint' | 'eslint' | 'typescript';
*/
export interface MutableParseSettings {
/**
* Code of the file being parsed.
* Code of the file being parsed, or raw source file containing it.
*/
code: string;
code: string | ts.SourceFile;

/**
* Full text of the file being parsed.
*/
codeFullText: string;

/**
* Whether the `comment` parse option is enabled.
Expand Down
4 changes: 2 additions & 2 deletions packages/typescript-estree/src/parser.ts
Expand Up @@ -75,7 +75,7 @@ function parse<T extends TSESTreeOptions = TSESTreeOptions>(
}

function parseWithNodeMapsInternal<T extends TSESTreeOptions = TSESTreeOptions>(
code: string,
code: string | ts.SourceFile,
options: T | undefined,
shouldPreserveNodeMaps: boolean,
): ParseWithNodeMapsResult<T> {
Expand Down Expand Up @@ -128,7 +128,7 @@ function clearParseAndGenerateServicesCalls(): void {
}

function parseAndGenerateServices<T extends TSESTreeOptions = TSESTreeOptions>(
code: string,
code: string | ts.SourceFile,
options: T,
): ParseAndGenerateServicesResult<T> {
/**
Expand Down
17 changes: 17 additions & 0 deletions packages/typescript-estree/src/source-files.ts
@@ -0,0 +1,17 @@
import * as ts from 'typescript';

export function isSourceFile(code: unknown): code is ts.SourceFile {
if (typeof code !== 'object' || code == null) {
return false;
}

const maybeSourceFile = code as Partial<ts.SourceFile>;
return (
maybeSourceFile.kind === ts.SyntaxKind.SourceFile &&
typeof maybeSourceFile.getFullText === 'function'
);
}

export function getCodeText(code: string | ts.SourceFile): string {
return isSourceFile(code) ? code.getFullText(code) : code;
}
1 change: 1 addition & 0 deletions packages/website/src/components/linter/config.ts
Expand Up @@ -2,6 +2,7 @@ import type { ParseSettings } from '@typescript-eslint/typescript-estree/dist/pa

export const parseSettings: ParseSettings = {
code: '',
codeFullText: '',
comment: true,
comments: [],
createDefaultProgram: false,
Expand Down