diff --git a/eslint.config.mjs b/eslint.config.mjs index cf16199c96ba..fa6080adb037 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -33,6 +33,8 @@ const vitestFiles = [ 'packages/eslint-plugin-internal/tests/**/*.test.{ts,tsx,cts,mts}', 'packages/typescript-eslint/tests/**/*.test.{ts,tsx,cts,mts}', 'packages/visitor-keys/tests/**/*.test.{ts,tsx,cts,mts}', + 'packages/parser/tests/lib/**/*.test.{ts,tsx,cts,mts}', + 'packages/parser/tests/test-utils/**/*.{ts,tsx,cts,mts}', ]; export default tseslint.config( @@ -379,18 +381,13 @@ export default tseslint.config( // define the vitest globals for all test files { files: vitestFiles, - languageOptions: { - globals: { - ...vitestPlugin.environments.env.globals, - }, - }, + ...vitestPlugin.configs.env, }, // test file specific configuration { files: [ 'packages/*/tests/**/*.test.{ts,tsx,cts,mts}', 'packages/*/tests/**/test.{ts,tsx,cts,mts}', - 'packages/parser/tests/**/*.{ts,tsx,cts,mts}', 'packages/integration-tests/tools/integration-test-base.ts', 'packages/integration-tests/tools/pack-packages.ts', ], @@ -440,6 +437,7 @@ export default tseslint.config( 'vitest/no-identical-title': 'error', 'vitest/no-test-prefixes': 'error', 'vitest/no-test-return-statement': 'error', + 'vitest/prefer-describe-function-title': 'error', 'vitest/prefer-each': 'error', 'vitest/prefer-spy-on': 'error', 'vitest/prefer-to-be': 'error', diff --git a/knip.ts b/knip.ts index d3fbad90f9a8..e3bd837e2e7e 100644 --- a/knip.ts +++ b/knip.ts @@ -65,6 +65,11 @@ export default { }, 'packages/parser': { ignore: ['tests/fixtures/**'], + + vitest: { + config: ['vitest.config.mts'], + entry: ['tests/lib/**/*.{bench,test,test-d}.?(c|m)ts?(x)'], + }, }, 'packages/rule-tester': { ignore: ['typings/eslint.d.ts'], diff --git a/package.json b/package.json index f2a4c9ad6700..b41ff68c1177 100644 --- a/package.json +++ b/package.json @@ -90,7 +90,7 @@ "@typescript-eslint/typescript-estree": "workspace:^", "@typescript-eslint/utils": "workspace:^", "@vitest/coverage-v8": "^3.1.1", - "@vitest/eslint-plugin": "^1.1.39", + "@vitest/eslint-plugin": "^1.1.42", "console-fail-test": "^0.5.0", "cross-fetch": "^4.0.0", "cspell": "^8.15.2", @@ -126,7 +126,7 @@ "tsx": "*", "typescript": ">=4.8.4 <5.9.0", "typescript-eslint": "workspace:^", - "vite": "^6.2.5", + "vite": "^6.2.6", "vitest": "^3.1.1", "yargs": "17.7.2" }, diff --git a/packages/parser/jest.config.js b/packages/parser/jest.config.js deleted file mode 100644 index 7dd5748f98bb..000000000000 --- a/packages/parser/jest.config.js +++ /dev/null @@ -1,8 +0,0 @@ -'use strict'; - -// @ts-check -/** @type {import('@jest/types').Config.InitialOptions} */ -module.exports = { - ...require('../../jest.config.base.js'), - testRegex: './tests/lib/.+\\.test\\.ts$', -}; diff --git a/packages/parser/package.json b/packages/parser/package.json index 9310828dd44a..4e1b843e7c0b 100644 --- a/packages/parser/package.json +++ b/packages/parser/package.json @@ -43,10 +43,10 @@ "build": "tsc -b tsconfig.build.json", "postbuild": "downlevel-dts dist _ts4.3/dist --to=4.3", "clean": "tsc -b tsconfig.build.json --clean", - "postclean": "rimraf dist && rimraf _ts4.3 && rimraf coverage", + "postclean": "rimraf dist/ _ts4.3/ coverage/", "format": "prettier --write \"./**/*.{ts,mts,cts,tsx,js,mjs,cjs,jsx,json,md,css}\" --ignore-path ../../.prettierignore", "lint": "npx nx lint", - "test": "jest", + "test": "vitest --run --config=$INIT_CWD/vitest.config.mts", "check-types": "npx nx typecheck" }, "peerDependencies": { @@ -61,13 +61,13 @@ "debug": "^4.3.4" }, "devDependencies": { - "@jest/types": "29.6.3", + "@vitest/coverage-v8": "^3.1.1", "downlevel-dts": "*", "glob": "*", - "jest": "29.7.0", "prettier": "^3.2.5", "rimraf": "*", - "typescript": "*" + "typescript": "*", + "vitest": "^3.1.1" }, "funding": { "type": "opencollective", diff --git a/packages/parser/project.json b/packages/parser/project.json index 94b5289ff17e..6b9e1efe5f09 100644 --- a/packages/parser/project.json +++ b/packages/parser/project.json @@ -1,12 +1,16 @@ { "name": "parser", "$schema": "../../node_modules/nx/schemas/project-schema.json", - "type": "library", - "implicitDependencies": [], + "projectType": "library", + "root": "packages/parser", + "sourceRoot": "packages/parser/src", "targets": { "lint": { "executor": "@nx/eslint:lint", "outputs": ["{options.outputFile}"] + }, + "test": { + "executor": "@nx/vite:test" } } } diff --git a/packages/parser/tests/lib/__snapshots__/services.test.ts.snap b/packages/parser/tests/lib/__snapshots__/services.test.ts.snap index e9e804b60eba..640ac5aedb34 100644 --- a/packages/parser/tests/lib/__snapshots__/services.test.ts.snap +++ b/packages/parser/tests/lib/__snapshots__/services.test.ts.snap @@ -1,6 +1,6 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`services fixtures/isolated-file.src 1`] = ` +exports[`services > fixtures/isolated-file.src 1`] = ` { "body": [ { diff --git a/packages/parser/tests/lib/parser.test.ts b/packages/parser/tests/lib/parser.test.ts index 4da882979b29..39c072a8d42e 100644 --- a/packages/parser/tests/lib/parser.test.ts +++ b/packages/parser/tests/lib/parser.test.ts @@ -9,7 +9,7 @@ import { parse, parseForESLint } from '../../src/parser'; describe('parser', () => { beforeEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); it('parse() should return just the AST from parseForESLint()', () => { @@ -24,7 +24,7 @@ describe('parser', () => { it('parseAndGenerateServices() should be called with options', () => { const code = 'const valid = true;'; - const spy = jest.spyOn(typescriptESTree, 'parseAndGenerateServices'); + const spy = vi.spyOn(typescriptESTree, 'parseAndGenerateServices'); const config: ParserOptions = { ecmaFeatures: { globalReturn: false, @@ -36,11 +36,10 @@ describe('parser', () => { extraFileExtensions: ['.foo'], filePath: './isolated-file.src.ts', project: 'tsconfig.json', - tsconfigRootDir: path.resolve(__dirname, '../fixtures/services'), + tsconfigRootDir: path.join(__dirname, '..', 'fixtures', 'services'), }; parseForESLint(code, config); - expect(spy).toHaveBeenCalledTimes(1); - expect(spy).toHaveBeenLastCalledWith(code, { + expect(spy).toHaveBeenCalledExactlyOnceWith(code, { comment: true, jsx: false, loc: true, @@ -52,9 +51,9 @@ describe('parser', () => { it('overrides `errorOnTypeScriptSyntacticAndSemanticIssues: false` when provided `errorOnTypeScriptSyntacticAndSemanticIssues: false`', () => { const code = 'const valid = true;'; - const spy = jest.spyOn(typescriptESTree, 'parseAndGenerateServices'); + const spy = vi.spyOn(typescriptESTree, 'parseAndGenerateServices'); parseForESLint(code, { errorOnTypeScriptSyntacticAndSemanticIssues: true }); - expect(spy).toHaveBeenCalledWith(code, { + expect(spy).toHaveBeenCalledExactlyOnceWith(code, { comment: true, ecmaFeatures: {}, errorOnTypeScriptSyntacticAndSemanticIssues: false, @@ -68,9 +67,9 @@ describe('parser', () => { it('sets `loggerFn: false` on typescript-estree when provided `warnOnUnsupportedTypeScriptVersion: false`', () => { const code = 'const valid = true;'; - const spy = jest.spyOn(typescriptESTree, 'parseAndGenerateServices'); + const spy = vi.spyOn(typescriptESTree, 'parseAndGenerateServices'); parseForESLint(code, { warnOnUnsupportedTypeScriptVersion: false }); - expect(spy).toHaveBeenCalledWith(code, { + expect(spy).toHaveBeenCalledExactlyOnceWith(code, { comment: true, ecmaFeatures: {}, errorOnTypeScriptSyntacticAndSemanticIssues: false, @@ -86,9 +85,9 @@ describe('parser', () => { it('sets `loggerFn: false` on typescript-estree when provided `warnOnUnsupportedTypeScriptVersion: true`', () => { const code = 'const valid = true;'; - const spy = jest.spyOn(typescriptESTree, 'parseAndGenerateServices'); + const spy = vi.spyOn(typescriptESTree, 'parseAndGenerateServices'); parseForESLint(code, { warnOnUnsupportedTypeScriptVersion: true }); - expect(spy).toHaveBeenCalledWith(code, { + expect(spy).toHaveBeenCalledExactlyOnceWith(code, { comment: true, ecmaFeatures: {}, errorOnTypeScriptSyntacticAndSemanticIssues: false, @@ -103,18 +102,17 @@ describe('parser', () => { it('should call analyze() with inferred analyze options when no analyze options are provided', () => { const code = 'const valid = true;'; - const spy = jest.spyOn(scopeManager, 'analyze'); + const spy = vi.spyOn(scopeManager, 'analyze'); const config: ParserOptions = { errorOnTypeScriptSyntacticAndSemanticIssues: false, filePath: 'isolated-file.src.ts', project: 'tsconfig.json', - tsconfigRootDir: path.join(__dirname, '../fixtures/services'), + tsconfigRootDir: path.join(__dirname, '..', 'fixtures', 'services'), }; parseForESLint(code, config); - expect(spy).toHaveBeenCalledTimes(1); - expect(spy).toHaveBeenLastCalledWith(expect.anything(), { + expect(spy).toHaveBeenCalledExactlyOnceWith(expect.anything(), { globalReturn: undefined, jsxFragmentName: undefined, jsxPragma: undefined, @@ -123,7 +121,7 @@ describe('parser', () => { }); }); - it.each([ + it.for([ ['esnext.full', ScriptTarget.ESNext], ['es2022.full', ScriptTarget.ES2022], ['es2021.full', ScriptTarget.ES2021], @@ -135,32 +133,32 @@ describe('parser', () => { ['es6', ScriptTarget.ES2015], ['lib', ScriptTarget.ES5], ['lib', undefined], - ])( + ] as const)( 'calls analyze() with `lib: [%s]` when the compiler options target is %s', - (lib, target) => { + ([lib, target], { expect }) => { const code = 'const valid = true;'; - const spy = jest.spyOn(scopeManager, 'analyze'); + const spy = vi.spyOn(scopeManager, 'analyze'); const config: ParserOptions = { filePath: 'isolated-file.src.ts', project: 'tsconfig.json', - tsconfigRootDir: path.join(__dirname, '../fixtures/services'), + tsconfigRootDir: path.join(__dirname, '..', 'fixtures', 'services'), }; - jest - .spyOn(typescriptESTree, 'parseAndGenerateServices') - .mockReturnValueOnce({ - ast: {}, - services: { - program: { - getCompilerOptions: () => ({ target }), - }, + vi.spyOn( + typescriptESTree, + 'parseAndGenerateServices', + ).mockReturnValueOnce({ + ast: {}, + services: { + program: { + getCompilerOptions: () => ({ target }), }, - } as typescriptESTree.ParseAndGenerateServicesResult); + }, + } as typescriptESTree.ParseAndGenerateServicesResult); parseForESLint(code, config); - expect(spy).toHaveBeenCalledTimes(1); - expect(spy).toHaveBeenLastCalledWith( + expect(spy).toHaveBeenCalledExactlyOnceWith( expect.anything(), expect.objectContaining({ lib: [lib], @@ -171,7 +169,7 @@ describe('parser', () => { it('calls analyze() with the provided analyze options when analyze options are provided', () => { const code = 'const valid = true;'; - const spy = jest.spyOn(scopeManager, 'analyze'); + const spy = vi.spyOn(scopeManager, 'analyze'); const config: ParserOptions = { ecmaFeatures: { globalReturn: false, @@ -187,13 +185,12 @@ describe('parser', () => { extraFileExtensions: ['.foo'], filePath: 'isolated-file.src.ts', project: 'tsconfig.json', - tsconfigRootDir: path.join(__dirname, '../fixtures/services'), + tsconfigRootDir: path.join(__dirname, '..', 'fixtures', 'services'), }; parseForESLint(code, config); - expect(spy).toHaveBeenCalledTimes(1); - expect(spy).toHaveBeenLastCalledWith(expect.anything(), { + expect(spy).toHaveBeenCalledExactlyOnceWith(expect.anything(), { globalReturn: false, jsxFragmentName: 'Bar', jsxPragma: 'Foo', diff --git a/packages/parser/tests/lib/services.test.ts b/packages/parser/tests/lib/services.test.ts index 3b3121017610..3719c7127e82 100644 --- a/packages/parser/tests/lib/services.test.ts +++ b/packages/parser/tests/lib/services.test.ts @@ -1,6 +1,6 @@ import { createProgram } from '@typescript-eslint/typescript-estree'; import * as glob from 'glob'; -import fs from 'node:fs'; +import fs from 'node:fs/promises'; import path from 'node:path'; import type { ParserOptions } from '../../src/parser'; @@ -15,7 +15,14 @@ import { // Setup //------------------------------------------------------------------------------ -const FIXTURES_DIR = './tests/fixtures/services'; +const FIXTURES_DIR = path.join( + __dirname, + '..', + '..', + 'tests', + 'fixtures', + 'services', +); const testFiles = glob.sync(`**/*.src.ts`, { cwd: FIXTURES_DIR, }); @@ -32,18 +39,19 @@ function createConfig(filename: string): ParserOptions { // Tests //------------------------------------------------------------------------------ -describe('services', () => { - const program = createProgram(path.resolve(FIXTURES_DIR, 'tsconfig.json')); - testFiles.forEach(filename => { - const code = fs.readFileSync(path.join(FIXTURES_DIR, filename), 'utf8'); - const config = createConfig(filename); - const snapshotName = formatSnapshotName(filename, FIXTURES_DIR, '.ts'); - it(snapshotName, createSnapshotTestBlock(code, config)); - it(`${snapshotName} services`, () => { - testServices(code, config); - }); - it(`${snapshotName} services with provided program`, () => { - testServices(code, { ...config, program }); - }); +const program = createProgram(path.resolve(FIXTURES_DIR, 'tsconfig.json')); + +describe.for(testFiles)('services', async filename => { + const code = await fs.readFile(path.join(FIXTURES_DIR, filename), { + encoding: 'utf-8', + }); + const config = createConfig(filename); + const snapshotName = formatSnapshotName(filename, FIXTURES_DIR, '.ts'); + it(snapshotName, createSnapshotTestBlock(code, config)); + it(`${snapshotName} services`, () => { + testServices(code, config); + }); + it(`${snapshotName} services with provided program`, () => { + testServices(code, { ...config, program }); }); }); diff --git a/packages/parser/tests/lib/tsx.test.ts b/packages/parser/tests/lib/tsx.test.ts index 5b6922e2c8f3..f58a859f41f7 100644 --- a/packages/parser/tests/lib/tsx.test.ts +++ b/packages/parser/tests/lib/tsx.test.ts @@ -1,5 +1,3 @@ -import type { ParserOptions } from '@typescript-eslint/types'; - import { parseForESLint } from '../../src/parser'; import { serializer } from '../test-utils/ts-error-serializer'; @@ -9,20 +7,12 @@ import { serializer } from '../test-utils/ts-error-serializer'; expect.addSnapshotSerializer(serializer); -function parseWithError(code: string, options?: ParserOptions | null): unknown { - try { - return parseForESLint(code, options); - } catch (e) { - return e; - } -} - describe('TSX', () => { describe("if the filename ends with '.tsx', enable jsx option automatically.", () => { it('filePath was not provided', () => { const code = 'const element = '; - expect(parseWithError(code)).toMatchInlineSnapshot(` + expect(() => parseForESLint(code)).toThrowErrorMatchingInlineSnapshot(` TSError { "column": 18, "index": 18, @@ -45,11 +35,11 @@ describe('TSX', () => { it('test.ts', () => { const code = 'const element = '; - expect( - parseWithError(code, { + expect(() => + parseForESLint(code, { filePath: 'test.ts', }), - ).toMatchInlineSnapshot(` + ).toThrowErrorMatchingInlineSnapshot(` TSError { "column": 18, "index": 18, @@ -62,14 +52,14 @@ describe('TSX', () => { it("test.ts with 'jsx:true' option", () => { const code = 'const element = '; - expect( - parseWithError(code, { + expect(() => + parseForESLint(code, { ecmaFeatures: { jsx: true, }, filePath: 'test.ts', }), - ).toMatchInlineSnapshot(` + ).toThrowErrorMatchingInlineSnapshot(` TSError { "column": 18, "index": 18, diff --git a/packages/parser/tsconfig.build.json b/packages/parser/tsconfig.build.json index dbcb0b0ac6c0..72d5737a808c 100644 --- a/packages/parser/tsconfig.build.json +++ b/packages/parser/tsconfig.build.json @@ -9,7 +9,7 @@ "types": ["node"] }, "include": ["src/**/*.ts", "typings"], - "exclude": ["jest.config.js", "src/**/*.spec.ts", "src/**/*.test.ts"], + "exclude": ["vitest.config.mts", "src/**/*.spec.ts", "src/**/*.test.ts"], "references": [ { "path": "../visitor-keys/tsconfig.build.json" diff --git a/packages/parser/tsconfig.spec.json b/packages/parser/tsconfig.spec.json index e3efdafbc520..be15ae29f809 100644 --- a/packages/parser/tsconfig.spec.json +++ b/packages/parser/tsconfig.spec.json @@ -3,10 +3,12 @@ "compilerOptions": { "outDir": "../../dist/out-tsc/packages/parser", "module": "NodeNext", - "types": ["jest", "node"] + "resolveJsonModule": true, + "types": ["node", "vitest/globals", "vitest/importMeta"] }, "include": [ - "jest.config.js", + "vitest.config.mts", + "package.json", "src/**/*.test.ts", "src/**/*.spec.ts", "src/**/*.d.ts", @@ -16,6 +18,9 @@ "references": [ { "path": "./tsconfig.build.json" + }, + { + "path": "../../tsconfig.spec.json" } ] } diff --git a/packages/parser/vitest.config.mts b/packages/parser/vitest.config.mts new file mode 100644 index 000000000000..be96a32226e6 --- /dev/null +++ b/packages/parser/vitest.config.mts @@ -0,0 +1,21 @@ +import * as path from 'node:path'; +import { defineProject, mergeConfig } from 'vitest/config'; + +import { vitestBaseConfig } from '../../vitest.config.base.mjs'; +import packageJson from './package.json' with { type: 'json' }; + +const vitestConfig = mergeConfig( + vitestBaseConfig, + + defineProject({ + root: import.meta.dirname, + + test: { + dir: path.join(import.meta.dirname, 'tests', 'lib'), + name: packageJson.name.replace('@typescript-eslint/', ''), + root: import.meta.dirname, + }, + }), +); + +export default vitestConfig; diff --git a/yarn.lock b/yarn.lock index 05d7f2fb704d..6d266a2aac3c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6130,18 +6130,18 @@ __metadata: version: 0.0.0-use.local resolution: "@typescript-eslint/parser@workspace:packages/parser" dependencies: - "@jest/types": 29.6.3 "@typescript-eslint/scope-manager": 8.29.1 "@typescript-eslint/types": 8.29.1 "@typescript-eslint/typescript-estree": 8.29.1 "@typescript-eslint/visitor-keys": 8.29.1 + "@vitest/coverage-v8": ^3.1.1 debug: ^4.3.4 downlevel-dts: "*" glob: "*" - jest: 29.7.0 prettier: ^3.2.5 rimraf: "*" typescript: "*" + vitest: ^3.1.1 peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: ">=4.8.4 <5.9.0" @@ -6282,7 +6282,7 @@ __metadata: "@typescript-eslint/typescript-estree": "workspace:^" "@typescript-eslint/utils": "workspace:^" "@vitest/coverage-v8": ^3.1.1 - "@vitest/eslint-plugin": ^1.1.39 + "@vitest/eslint-plugin": ^1.1.42 console-fail-test: ^0.5.0 cross-fetch: ^4.0.0 cspell: ^8.15.2 @@ -6318,7 +6318,7 @@ __metadata: tsx: "*" typescript: ">=4.8.4 <5.9.0" typescript-eslint: "workspace:^" - vite: ^6.2.5 + vite: ^6.2.6 vitest: ^3.1.1 yargs: 17.7.2 languageName: unknown @@ -6446,20 +6446,18 @@ __metadata: languageName: node linkType: hard -"@vitest/eslint-plugin@npm:^1.1.39": - version: 1.1.39 - resolution: "@vitest/eslint-plugin@npm:1.1.39" +"@vitest/eslint-plugin@npm:^1.1.42": + version: 1.1.42 + resolution: "@vitest/eslint-plugin@npm:1.1.42" peerDependencies: - "@typescript-eslint/utils": ^8.24.0 + "@typescript-eslint/utils": ">= 8.24.0" eslint: ">= 8.57.0" typescript: ">= 5.0.0" vitest: "*" peerDependenciesMeta: typescript: optional: true - vitest: - optional: true - checksum: 0730c7d2a24b6e72ad74478991f7426fe889d0a326f4dc5034db5bfb1fcedeb1f54f8d90d61587e66c447139fa5c72af07003d740753860de82a9b0565bd14aa + checksum: 0b78745fde3cd6c35ce2cecd097133a41ff0c3787f9827538be1196e54b22d41a63c8ce2ac4571f29132399e96d6cf5ee47dcaf8a8f618bed8de80ad86851046 languageName: node linkType: hard @@ -19431,9 +19429,9 @@ __metadata: linkType: hard "std-env@npm:^3.8.1": - version: 3.8.1 - resolution: "std-env@npm:3.8.1" - checksum: 20114a5270aa2a3fc50d897461c6ab73329cf2d3c6bff1c124bb969577493aeebda8ee1916588b2657afcee9881bc652437cfdec6360e3f30be36c8675ea0cbb + version: 3.9.0 + resolution: "std-env@npm:3.9.0" + checksum: d40126e4a650f6e5456711e6c297420352a376ef99a9599e8224d2d8f2ff2b91a954f3264fcef888d94fce5c9ae14992c5569761c95556fc87248ce4602ed212 languageName: node linkType: hard @@ -20895,9 +20893,9 @@ __metadata: languageName: node linkType: hard -"vite@npm:^5.0.0 || ^6.0.0, vite@npm:^6.2.5": - version: 6.2.5 - resolution: "vite@npm:6.2.5" +"vite@npm:^5.0.0 || ^6.0.0, vite@npm:^6.2.6": + version: 6.2.6 + resolution: "vite@npm:6.2.6" dependencies: esbuild: ^0.25.0 fsevents: ~2.3.3 @@ -20943,7 +20941,7 @@ __metadata: optional: true bin: vite: bin/vite.js - checksum: 49a6529c5ae8d6e4926f2daa51d7e20c50d780d8d2ec8c08605e966983fe8d17ec69bc36a356c1a21141c5a630b7a4109f3690c5b33f579d3e2bf26f914a149d + checksum: ddeb36d29c053c6d6f0e70eb01939848db611135878d85e9497fc4b899667f58ce35ea4014acf01342ee1cf115879280fac809c0a806ad6432833cde87fe90dc languageName: node linkType: hard pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy