Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
"@typescript-eslint/types": "workspace:^",
"@typescript-eslint/typescript-estree": "workspace:^",
"@typescript-eslint/utils": "workspace:^",
"@typescript/vfs": "^1.6.2",
"@vitest/coverage-v8": "^3.1.3",
"@vitest/eslint-plugin": "1.5.1",
"console-fail-test": "^0.6.0",
Expand Down
1 change: 1 addition & 0 deletions packages/eslint-plugin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
"@types/natural-compare": "*",
"@typescript-eslint/rule-schema-to-typescript-types": "8.50.0",
"@typescript-eslint/rule-tester": "8.50.0",
"@typescript/vfs": "^1.6.2",
"@vitest/coverage-v8": "^3.1.3",
"ajv": "^6.12.6",
"cross-fetch": "*",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ export default createRule<Options, MessageIds>({
node.source.value,
context.filename,
services.program.getCompilerOptions(),
ts.sys,
services.host,
);
if (sourceModule.resolvedModule == null) {
return;
Expand Down
56 changes: 56 additions & 0 deletions packages/eslint-plugin/tests/rules/consistent-type-exports.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,65 @@ import { noFormat } from '@typescript-eslint/rule-tester';

import rule from '../../src/rules/consistent-type-exports';
import { createRuleTesterWithTypes } from '../RuleTester';
import * as vfs from '../vfs';

const sys = vfs.fixture`
// @filename: /tsconfig.json
{
"compilerOptions": {
"jsx": "preserve",
"target": "es5",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"lib": ["es2015", "es2017", "esnext"],
"types": [],
"experimentalDecorators": true
},
"exclude": ["/lib*", "node_modules"]
}
// @filename: /consistent-type-exports/index.ts
export type Type1 = 1;
export type Type2 = 1;
export const value1 = 2;
export const value2 = 2;
export class Class1 {}
// @filename: /consistent-type-exports/type-only-exports.ts
export type TypeFoo = 1;
export interface InterfaceFoo {
foo: 'bar';
}
class LocalClass {}
export type { LocalClass };
// @filename: /consistent-type-exports/type-only-reexport.ts
export * from './type-only-exports';
export type * as typeOnlyExports from './type-only-exports';
export type * from './index';
export type * as indexExports from './index';
export { Type1 as AliasedType1 } from './index';
import { Class1 } from './index';
export { type Class1 as AliasedClass1 };
// @filename: /consistent-type-exports/value-reexport.ts
export * from './index';
`;

const ruleTester = createRuleTesterWithTypes({
project: './tsconfig.json',
sys,
tsconfigRootDir: '/',
});

ruleTester.run('consistent-type-exports', rule, {
Expand Down
84 changes: 80 additions & 4 deletions packages/eslint-plugin/tests/rules/no-deprecated.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,88 @@
import rule from '../../src/rules/no-deprecated';
import { getFixturesRootDir, createRuleTesterWithTypes } from '../RuleTester';
import { createRuleTesterWithTypes } from '../RuleTester';
import * as vfs from '../vfs';

const sys = vfs.fixture`
// @filename: /tsconfig.json
{
"compilerOptions": {
"jsx": "preserve",
"target": "es5",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"lib": ["es2015", "es2017", "esnext"],
"types": ["node", "react"],
"experimentalDecorators": true
},
"exclude": ["/lib*", "node_modules"]
}

const rootDir = getFixturesRootDir();
// @filename: /tsconfig.moduleresolution-node16.json
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "node16",
"moduleResolution": "node16"
}
}
// @filename: /deprecated.ts
/** @deprecated */
export class DeprecatedClass {
/** @deprecated */
foo: string = '';
}
/** @deprecated */
export const deprecatedVariable = 1;
/** @deprecated */
export function deprecatedFunction(): void {}
class NormalClass {}
const normalVariable = 1;
function normalFunction(): void;
function normalFunction(arg: string): void;
function normalFunction(arg?: string): void {}
function deprecatedFunctionWithOverloads(): void;
/** @deprecated */
function deprecatedFunctionWithOverloads(arg: string): void;
function deprecatedFunctionWithOverloads(arg?: string): void {}
export class ClassWithDeprecatedConstructor {
constructor();
/** @deprecated */
constructor(arg: string);
constructor(arg?: string) {}
}
export {
/** @deprecated */
NormalClass,
/** @deprecated */
normalVariable,
/** @deprecated */
normalFunction,
deprecatedFunctionWithOverloads,
/** @deprecated Reason */
deprecatedFunctionWithOverloads as reexportedDeprecatedFunctionWithOverloads,
/** @deprecated Reason */
ClassWithDeprecatedConstructor as ReexportedClassWithDeprecatedConstructor,
};

/** @deprecated Reason */
export type T = { a: string };

export type U = { b: string };

/** @deprecated */
export default {
foo: 1,
};
`;
const rootDir = '/';
const ruleTester = createRuleTesterWithTypes({
ecmaFeatures: {
jsx: true,
},
project: './tsconfig.json',
sys,
tsconfigRootDir: rootDir,
});

ruleTester.run('no-deprecated', rule, {
Expand Down Expand Up @@ -438,7 +514,7 @@ ruleTester.run('no-deprecated', rule, {
`,
languageOptions: {
parserOptions: {
project: './tsconfig.moduleResolution-node16.json',
project: './tsconfig.moduleresolution-node16.json',
projectService: false,
tsconfigRootDir: rootDir,
},
Expand Down Expand Up @@ -3333,7 +3409,7 @@ exists('/foo');
],
languageOptions: {
parserOptions: {
project: './tsconfig.moduleResolution-node16.json',
project: './tsconfig.moduleresolution-node16.json',
projectService: false,
tsconfigRootDir: rootDir,
},
Expand Down
46 changes: 46 additions & 0 deletions packages/eslint-plugin/tests/vfs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import type { System } from 'typescript';

import {
addAllFilesFromFolder,
createDefaultMapFromNodeModules,
createSystem,
} from '@typescript/vfs';
import path from 'node:path';

const fsMap: Map<string, string> = createDefaultMapFromNodeModules({});
addAllFilesFromFolder(
fsMap,
path.join(__dirname, '../../../node_modules/@types'),
);

function parseVirtualFiles(code: string): [string, string][] {
const result: [string, string][] = [];

const lines = code.split(/\r?\n/);
const cmd = '// @filename: ';

let currentFileLines: string[] = [];
const files: [string, string[]][] = [];
for (const line of lines) {
if (line.startsWith(cmd)) {
currentFileLines = [];
files.push([line.slice(cmd.length).trim(), currentFileLines]);
} else {
currentFileLines.push(line);
}
}
for (const [fileName, fileLines] of files) {
result.push([fileName, fileLines.join('\n')]);
}
return result;
}

export function fixture(code: TemplateStringsArray, ...keys: string[]): System {
const files = parseVirtualFiles(String.raw(code, ...keys));
return {
...createSystem(new Map([...fsMap, ...files])),
getExecutingFilePath: () => {
return '/';
},
};
}
1 change: 1 addition & 0 deletions packages/parser/src/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ export function parseForESLint(
comment: true,
loc: true,
range: true,
sys: parserOptions.sys,
tokens: true,
} satisfies TSESTreeOptions;

Expand Down
16 changes: 10 additions & 6 deletions packages/project-service/src/createProjectService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ export interface CreateProjectServiceSettings {
/**
* Creates a new Project Service instance, as well as metadata on its creation.
* @param settings Settings to create a new Project Service instance.
* @param sys An instance used for interacting with the file system.
* @returns A new Project Service instance, as well as metadata on its creation.
* @example
* ```ts
Expand All @@ -90,11 +91,14 @@ export interface CreateProjectServiceSettings {
* service.openClientFile('index.ts');
* ```
*/
export function createProjectService({
jsDocParsingMode,
options: optionsRaw = {},
tsconfigRootDir,
}: CreateProjectServiceSettings = {}): ProjectServiceAndMetadata {
export function createProjectService(
{
jsDocParsingMode,
options: optionsRaw = {},
tsconfigRootDir,
}: CreateProjectServiceSettings = {},
sys?: ts.System,
): ProjectServiceAndMetadata {
const options = {
defaultProject: 'tsconfig.json',
...optionsRaw,
Expand All @@ -110,7 +114,7 @@ export function createProjectService({
// there's a whole separate update pass in maybeInvalidateProgram at the bottom of getWatchProgramsForProjects
// (this "goes nuclear on TypeScript")
const system: ts.server.ServerHost = {
...tsserver.sys,
...(sys ?? tsserver.sys),
clearImmediate,
clearTimeout,
setImmediate,
Expand Down
6 changes: 6 additions & 0 deletions packages/rule-tester/src/RuleTester.ts
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,9 @@ export class RuleTester extends TestFramework {
filename,
);
}
if (resolvedOptions.sys) {
return path.join(resolvedOptions.sys.getCurrentDirectory(), filename);
}
return filename;
};
const normalizeTest = <
Expand Down Expand Up @@ -683,6 +686,9 @@ export class RuleTester extends TestFramework {
);
filename = item.filename;
}
if (filename) {
config.languageOptions.parserOptions?.sys?.writeFile(filename, code);
}

const prefixedRuleName = `${RULE_TESTER_PLUGIN_PREFIX}${ruleName}`;

Expand Down
3 changes: 2 additions & 1 deletion packages/types/src/parser-options.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Program } from 'typescript';
import type { Program, System } from 'typescript';

import type { Lib } from './lib';

Expand Down Expand Up @@ -113,6 +113,7 @@ export interface ParserOptions {
projectService?: boolean | ProjectServiceOptions;
range?: boolean;
sourceType?: SourceType | undefined;
sys?: System;
tokens?: boolean;
tsconfigRootDir?: string;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ function createWatchProgram(
const watchCompilerHost = ts.createWatchCompilerHost(
tsconfigPath,
createDefaultCompilerOptionsFromExtra(parseSettings),
ts.sys,
parseSettings.sys ?? ts.sys,
ts.createAbstractBuilder,
diagnosticReporter,
// TODO: file issue on TypeScript to suggest making optional?
Expand Down
5 changes: 4 additions & 1 deletion packages/typescript-estree/src/createParserServices.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import type * as ts from 'typescript';
import * as ts from 'typescript';

import type { ASTMaps } from './convert';
import type { ParserServices } from './parser-options';

export function createParserServices(
astMaps: ASTMaps,
program: ts.Program | null,
host?: ts.ModuleResolutionHost,
): ParserServices {
if (!program) {
return {
emitDecoratorMetadata: undefined,
experimentalDecorators: undefined,
host: null,
isolatedDeclarations: undefined,
program,
// we always return the node maps because
Expand All @@ -24,6 +26,7 @@ export function createParserServices(
const compilerOptions = program.getCompilerOptions();

return {
host: host ?? ts.sys,
program,
// not set in the config is the same as off
emitDecoratorMetadata: compilerOptions.emitDecoratorMetadata ?? false,
Expand Down
Loading
Loading