Skip to content

Commit 3f492a9

Browse files
authored
add parser plugin options for decorators (#99)
* upgrade dependency to the latest * add new options for decorators plugin * adding limited paserPluginOptions to parse function * update README * correct typo
1 parent 1727904 commit 3f492a9

File tree

11 files changed

+277
-60
lines changed

11 files changed

+277
-60
lines changed

README.md

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,49 @@ The engine that allows editors to build on top of Jest.
1010
This is only useful if you are interested in building an editor integration for Jest.
1111

1212
## API
13-
```
13+
```ts
1414
parse(
1515
filePath: string,
1616
serializedData?: string,
17-
strictMode: boolean = false,
18-
)
17+
options?: JESParserOptions
18+
);
19+
20+
JESParserOptions =
21+
{
22+
plugins?: JESParserPluginOptions;
23+
strictMode?: boolean;
24+
};
25+
26+
JESParserPluginOptions =
27+
{
28+
decorators?: 'legacy' | {
29+
decoratorsBeforeExport?: boolean;
30+
allowCallParenthesized?: boolean;
31+
}
32+
}
1933
```
34+
2035
Parse is a static Jest parser which uses Babel 7 and supports js,jsx,mjs,ts,tsx files.
2136

2237
[Supported ECMAScript proposals](https://github.com/babel/babel/blob/928b9f8c9518284eac6d0598633f2ec373fc6d0c/packages/babel-parser/typings/babel-parser.d.ts#L97)
2338

2439
- filePath = Path to the file you want to parse.
2540
- serializedData = Serialized data, will be used instead of the filePath if available (optional).
26-
- strictMode = If this option is activated the parser throws an exception if the filetype is not detected, defaults to false.
27-
41+
- options:
42+
- strictMode = If this option is activated the parser throws an exception if the filetype is not detected, defaults to false.
43+
- pluginOptions = allow override for selected [plugins](https://babeljs.io/docs/en/babel-parser#plugins) options. Currently only support `decorators`.
44+
45+
examples:
46+
```ts
47+
parse('test.spec.ts');
48+
parse('parameterDecorators.spec.ts', undefined, {plugins: {decorators: 'legacy'}})
49+
parse('parameterDecorators.spec.ts', undefined,
50+
{plugins:
51+
{decorators:
52+
{decoratorsBeforeExport: false}
53+
}
54+
})
55+
```
2856

2957
## Note
3058

index.d.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@ import {ChildProcess} from 'child_process';
1010
import {Config as JestConfig} from '@jest/types';
1111
import { CoverageMapData } from 'istanbul-lib-coverage';
1212
import ProjectWorkspace, {ProjectWorkspaceConfig, createProjectWorkspace, LoginShell } from './build/project_workspace';
13-
export {createProjectWorkspace, ProjectWorkspaceConfig, ProjectWorkspace, LoginShell};
1413
import {SourceLocation} from '@babel/types';
1514
import { SnapshotData } from 'jest-snapshot/build/types';
15+
import parse, {JESParserPluginOptions, JESParserOptions} from './build/parsers/';
16+
export {parse, JESParserPluginOptions, JESParserOptions, createProjectWorkspace, ProjectWorkspaceConfig, ProjectWorkspace, LoginShell};
1617
export interface RunArgs {
1718
args: string[];
1819
replace?: boolean; // default is false
@@ -59,8 +60,6 @@ export interface IParseResults {
5960
file: string;
6061
}
6162

62-
export function parse(file: string, data?: string, strict?: boolean): IParseResults;
63-
6463
export interface Location {
6564
column: number;
6665
line: number;
@@ -233,11 +232,16 @@ export interface SnapshotBlock{
233232
node: SnapshotNode;
234233
parents: SnapshotNode[];
235234
}
235+
export interface SnapshotParserOptions {
236+
verbose?: boolean;
237+
// $FlowIgnore[value-as-type]
238+
parserOptions?: JESParserOptions;
239+
}
236240
export class Snapshot {
237241
constructor(parser?: any, customMatchers?: string[]);
238-
getMetadata(filepath: string, verbose?: boolean): SnapshotMetadata[];
239-
getMetadataAsync(filePath: string, verbose?: boolean): Promise<Array<SnapshotMetadata>>;
240-
parse(filePath: string, verbose?: boolean): SnapshotBlock[];
242+
getMetadata(filepath: string, options?: SnapshotParserOptions): SnapshotMetadata[];
243+
getMetadataAsync(filePath: string, options?: SnapshotParserOptions): Promise<Array<SnapshotMetadata>>;
244+
parse(filePath: string, options?: SnapshotParserOptions): SnapshotBlock[];
241245
getSnapshotContent(filePath: string, name: string | RegExp): Promise<string | SnapshotData | undefined>;
242246
}
243247

src/Snapshot.js

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {buildSnapshotResolver, type SnapshotResolver, type SnapshotData, utils}
1313
import type {ProjectConfig} from '../types/Config';
1414

1515
import {getASTfor} from './parsers/babel_parser';
16+
import {JESParserOptions} from './parsers';
1617

1718
type Node = any;
1819

@@ -83,6 +84,11 @@ export interface SnapshotNode {
8384
parents: Node[];
8485
}
8586

87+
export interface SnapshotParserOptions {
88+
verbose?: boolean;
89+
// $FlowIgnore[value-as-type]
90+
parserOptions?: JESParserOptions;
91+
}
8692
export default class Snapshot {
8793
_parser: Function;
8894

@@ -107,12 +113,12 @@ export default class Snapshot {
107113
);
108114
}
109115

110-
parse(filePath: string, verbose: boolean = false): SnapshotNode[] {
116+
parse(filePath: string, options?: SnapshotParserOptions): SnapshotNode[] {
111117
let fileNode;
112118
try {
113-
fileNode = this._parser(filePath);
119+
fileNode = this._parser(filePath, undefined, options?.parserOptions);
114120
} catch (error) {
115-
if (verbose) {
121+
if (options?.verbose) {
116122
// eslint-disable-next-line no-console
117123
console.warn(error);
118124
}
@@ -183,17 +189,17 @@ export default class Snapshot {
183189
return Object.entries(data).length > 0 ? data : null;
184190
}
185191

186-
async getMetadataAsync(filePath: string, verbose: boolean = false): Promise<Array<SnapshotMetadata>> {
192+
async getMetadataAsync(filePath: string, options?: SnapshotParserOptions): Promise<Array<SnapshotMetadata>> {
187193
await this._getSnapshotResolver();
188-
return this.getMetadata(filePath, verbose);
194+
return this.getMetadata(filePath, options);
189195
}
190196

191-
getMetadata(filePath: string, verbose: boolean = false): Array<SnapshotMetadata> {
197+
getMetadata(filePath: string, options?: SnapshotParserOptions): Array<SnapshotMetadata> {
192198
if (!this.snapshotResolver) {
193199
throw new Error('snapshotResolver is not ready yet, consider migrating to "getMetadataAsync" instead');
194200
}
195201
const snapshotPath = this.snapshotResolver.resolveSnapshotPath(filePath);
196-
const snapshotNodes = this.parse(filePath, verbose);
202+
const snapshotNodes = this.parse(filePath, options);
197203
const snapshots = utils.getSnapshotData(snapshotPath, 'none').data;
198204

199205
let lastParent = null;
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
2+
// parameter decorator
3+
function first(
4+
target: any,
5+
propertyKey: string,
6+
parameterIndex: number,
7+
){};
8+
class Example {
9+
method1(
10+
@first
11+
_a: string
12+
){}
13+
}
14+
15+
fit('fit', () => {
16+
expect(fake).toMatchSnapshot();
17+
expect(fake).toMatchSnapshot();
18+
})

src/__tests__/parsers/babel_parser.test.ts

Lines changed: 109 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
import * as path from 'path';
1515
import {NamedBlock, ParseResult} from '../..';
1616
import {parse} from '../../parsers/babel_parser';
17-
import {parseOptions} from '../../parsers/helper';
17+
import {JESParserOptions, parseOptions} from '../../parsers/helper';
1818

1919
const fixtures = path.resolve('fixtures');
2020

@@ -26,10 +26,11 @@ describe('parsers', () => {
2626
${'whatever.ts'}
2727
${'whatever.tsx'}
2828
`('can parse file like $fileName', ({fileName}) => {
29-
const parseFunction = (file: string, data?: string) => parse(file, data, parseOptions(fileName));
29+
const parseFunction = (file: string, data?: string, options?: JESParserOptions) =>
30+
parse(file, data, parseOptions(fileName, options));
3031
const assertBlock = (block, start, end, name: string = null) => {
31-
expect(block.start).toEqual(start);
32-
expect(block.end).toEqual(end);
32+
expect(block.start).toEqual(expect.objectContaining(start));
33+
expect(block.end).toEqual(expect.objectContaining(end));
3334
if (name) {
3435
expect(block.name).toEqual(name);
3536
}
@@ -210,17 +211,17 @@ describe('parsers', () => {
210211
expect(data.expects.length).toEqual(8);
211212

212213
const firstExpect = data.expects[0];
213-
expect(firstExpect.start).toEqual({column: 5, line: 13});
214-
expect(firstExpect.end).toEqual({column: 36, line: 13});
214+
expect(firstExpect.start).toEqual(expect.objectContaining({column: 5, line: 13}));
215+
expect(firstExpect.end).toEqual(expect.objectContaining({column: 36, line: 13}));
215216
});
216217

217218
it('finds Expects in a danger flow test file ', () => {
218219
const data = parseFunction(`${fixtures}/dangerjs/github.example`);
219220
expect(data.expects.length).toEqual(3);
220221

221222
const thirdExpect = data.expects[2];
222-
expect(thirdExpect.start).toEqual({column: 5, line: 33});
223-
expect(thirdExpect.end).toEqual({column: 39, line: 33});
223+
expect(thirdExpect.start).toEqual(expect.objectContaining({column: 5, line: 33}));
224+
expect(thirdExpect.end).toEqual(expect.objectContaining({column: 39, line: 33}));
224225
});
225226

226227
it('finds Expects in a metaphysics test file', () => {
@@ -373,7 +374,7 @@ describe('parsers', () => {
373374
startCol: number,
374375
endLine: number,
375376
endCol: number,
376-
nameType = 'Literal',
377+
nameType = 'StringLiteral',
377378
lastProperty: string = null
378379
) => {
379380
expect(nBlock.name).toEqual(name);
@@ -464,7 +465,7 @@ describe('parsers', () => {
464465

465466
const itBlock = parseResult.itBlocks[0];
466467
assertBlock2(itBlock, 2, 7, 4, 9, 'each test %p');
467-
assertNameInfo(itBlock, 'each test %p', 2, 33, 2, 44, 'Literal', 'each');
468+
assertNameInfo(itBlock, 'each test %p', 2, 33, 2, 44, 'StringLiteral', 'each');
468469
});
469470
it('should be able to detect it.skip.each', () => {
470471
const data = `
@@ -478,7 +479,7 @@ describe('parsers', () => {
478479

479480
const itBlock = parseResult.itBlocks[0];
480481
assertBlock2(itBlock, 2, 7, 4, 9, 'each test %p');
481-
assertNameInfo(itBlock, 'each test %p', 2, 38, 2, 49, 'Literal', 'each');
482+
assertNameInfo(itBlock, 'each test %p', 2, 38, 2, 49, 'StringLiteral', 'each');
482483
});
483484

484485
it('For the simplest it.each cases', () => {
@@ -933,6 +934,83 @@ describe('parsers', () => {
933934
expect(parseResult.expects.length).toEqual(0);
934935
});
935936
});
937+
describe('pluginOptions', () => {
938+
describe('decorators', () => {
939+
it('legacy', () => {
940+
const data = `
941+
import { asMockedFunction, type AnyFunction } from '@whatever/jest-types';
942+
test('a test', () => {
943+
expect(true).toBe(true);
944+
});
945+
class SimpleTestController {
946+
handlerMethod(@Body() xxx) {
947+
return;
948+
}
949+
}
950+
`;
951+
const parseResult = parseFunction('whatever', data, {plugins: {decorators: 'legacy'}});
952+
expect(parseResult.itBlocks.length).toEqual(1);
953+
954+
const name = 'a test';
955+
const itBlock = parseResult.itBlocks[0];
956+
assertBlock2(itBlock, 3, 11, 5, 13, name);
957+
assertNameInfo(itBlock, name, 3, 17, 3, 22);
958+
});
959+
it('decoratorsBeforeExport', () => {
960+
const beforeExport = `
961+
test('a test', () => {
962+
expect(true).toBe(true);
963+
});
964+
@dec
965+
export class C {}
966+
`;
967+
const afterExport = `
968+
test('a test', () => {
969+
expect(true).toBe(true);
970+
});
971+
export @dec class C {}
972+
`;
973+
const beforePlugin = {decorators: {decoratorsBeforeExport: true}};
974+
const afterPlugin = {decorators: {decoratorsBeforeExport: false}};
975+
976+
let parseResult = parseFunction('whatever', beforeExport, {plugins: beforePlugin});
977+
expect(parseResult.itBlocks.length).toEqual(1);
978+
979+
parseResult = parseFunction('whatever', afterExport, {plugins: afterPlugin});
980+
expect(parseResult.itBlocks.length).toEqual(1);
981+
982+
expect(() => parseFunction('whatever', beforeExport, {plugins: afterPlugin})).toThrow();
983+
expect(() => parseFunction('whatever', afterExport, {plugins: beforePlugin})).toThrow();
984+
});
985+
it('allowCallParenthesized', () => {
986+
const callParenthesized = `
987+
test('a test', () => {
988+
expect(true).toBe(true);
989+
});
990+
@(dec)() class C {};
991+
`;
992+
const callNotParenthesized = `
993+
test('a test', () => {
994+
expect(true).toBe(true);
995+
});
996+
@(dec()) class C {};
997+
`;
998+
999+
const allowPlugin = {decorators: {allowCallParenthesized: true}};
1000+
const notAllowPlugin = {decorators: {allowCallParenthesized: false}};
1001+
1002+
let parseResult = parseFunction('whatever', callParenthesized, {plugins: allowPlugin});
1003+
expect(parseResult.itBlocks.length).toEqual(1);
1004+
1005+
parseResult = parseFunction('whatever', callNotParenthesized, {plugins: allowPlugin});
1006+
expect(parseResult.itBlocks.length).toEqual(1);
1007+
parseResult = parseFunction('whatever', callNotParenthesized, {plugins: notAllowPlugin});
1008+
expect(parseResult.itBlocks.length).toEqual(1);
1009+
1010+
expect(() => parseFunction('whatever', callParenthesized, {plugins: notAllowPlugin})).toThrow();
1011+
});
1012+
});
1013+
});
9361014
describe('parse error use case', () => {
9371015
it('https://github.com/jest-community/vscode-jest/issues/405', () => {
9381016
const data = `
@@ -1023,6 +1101,26 @@ describe('parsers', () => {
10231101
const parseResult = parseFunction('whatever', data);
10241102
expect(parseResult.itBlocks.length).toEqual(1);
10251103

1104+
const name = 'a test';
1105+
const itBlock = parseResult.itBlocks[0];
1106+
assertBlock2(itBlock, 3, 9, 5, 11, name);
1107+
assertNameInfo(itBlock, name, 3, 15, 3, 20);
1108+
});
1109+
it('https://github.com/jest-community/jest-editor-support/issues/89', () => {
1110+
const data = `
1111+
import { asMockedFunction, type AnyFunction } from '@whatever/jest-types';
1112+
test('a test', () => {
1113+
expect(true).toBe(true);
1114+
});
1115+
class SimpleTestController {
1116+
handlerMethod(@Body() xxx) {
1117+
return;
1118+
}
1119+
}
1120+
`;
1121+
const parseResult = parseFunction('whatever', data, {plugins: {decorators: 'legacy'}});
1122+
expect(parseResult.itBlocks.length).toEqual(1);
1123+
10261124
const name = 'a test';
10271125
const itBlock = parseResult.itBlocks[0];
10281126
assertBlock2(itBlock, 3, 9, 5, 11, name);

0 commit comments

Comments
 (0)