| import 'jasmine'; |
| import * as ts from 'typescript'; |
| import {Failure, Fix} from '../../failure'; |
| import {ConformancePatternRule, PatternKind} from '../../rules/conformance_pattern_rule'; |
| import {buildReplacementFixer, Fixer, maybeAddNamedImport, maybeAddNamespaceImport} from '../../util/fixer'; |
| import {compile, compileAndCheck, customMatchers} from '../../util/testing/test_support'; |
| |
| const uppercaseFixer: Fixer = { |
| getFixForFlaggedNode(node: ts.Node): Fix { |
| return { |
| changes: [{ |
| start: node.getStart(), |
| end: node.getEnd(), |
| replacement: node.getText().toUpperCase(), |
| sourceFile: node.getSourceFile(), |
| }] |
| }; |
| } |
| }; |
| |
| const uppercaseFixerBuilt: Fixer = buildReplacementFixer((node: ts.Node) => { |
| return {replaceWith: node.getText().toUpperCase()}; |
| }); |
| |
| // The initial config and source off which we run those checks. |
| const baseConfig = { |
| errorMessage: 'found citation', |
| kind: PatternKind.BANNED_PROPERTY_WRITE, |
| values: ['HTMLQuoteElement.prototype.cite'], |
| }; |
| |
| const source = `export {};\n` + |
| `const q = document.createElement('q');\n` + |
| `q.cite = 'some example string';\n`; |
| |
| describe('ConformancePatternRule\'s fixer', () => { |
| describe('Generates basic fixes', () => { |
| it('for a single match', () => { |
| const rule = new ConformancePatternRule(baseConfig, uppercaseFixer); |
| const results = compileAndCheck(rule, source); |
| |
| expect(results).toHaveFailuresMatching({ |
| matchedCode: `q.cite = 'some example string'`, |
| messageText: 'found citation', |
| fix: [ |
| {start: 50, end: 80, replacement: `Q.CITE = 'SOME EXAMPLE STRING'`} |
| ] |
| }); |
| }); |
| |
| it('for a single match (alternate fixer)', () => { |
| const rule = new ConformancePatternRule(baseConfig, uppercaseFixerBuilt); |
| const results = compileAndCheck(rule, source); |
| |
| expect(results).toHaveFailuresMatching({ |
| matchedCode: `q.cite = 'some example string'`, |
| messageText: 'found citation', |
| fix: [ |
| {start: 50, end: 80, replacement: `Q.CITE = 'SOME EXAMPLE STRING'`} |
| ] |
| }); |
| }); |
| |
| it('for several matches', () => { |
| const rule = new ConformancePatternRule(baseConfig, uppercaseFixer); |
| const sourceTwoMatches = |
| source + `q.cite = 'some other example string';\n`; |
| const results = compileAndCheck(rule, sourceTwoMatches); |
| |
| expect(results).toHaveFailuresMatching( |
| { |
| matchedCode: `q.cite = 'some example string'`, |
| messageText: 'found citation', |
| fix: [{ |
| start: 50, |
| end: 80, |
| replacement: `Q.CITE = 'SOME EXAMPLE STRING'` |
| }] |
| }, |
| { |
| matchedCode: `q.cite = 'some other example string'`, |
| messageText: 'found citation', |
| fix: [{ |
| start: 82, |
| end: 118, |
| replacement: `Q.CITE = 'SOME OTHER EXAMPLE STRING'` |
| }] |
| }); |
| |
| expect(results[0].fixToReadableStringInContext()) |
| .toBe( |
| `Suggested fix:\n` + |
| `- Replace the full match with: Q.CITE = 'SOME EXAMPLE STRING'`); |
| expect(results[1].fixToReadableStringInContext()) |
| .toBe( |
| `Suggested fix:\n` + |
| `- Replace the full match with: Q.CITE = 'SOME OTHER EXAMPLE STRING'`); |
| }); |
| }); |
| |
| describe('adds imports', () => { |
| const addNamedImportFixer: Fixer = { |
| getFixForFlaggedNode(n: ts.Node): Fix | |
| undefined { |
| const changes = []; |
| const ic1 = |
| maybeAddNamedImport(n.getSourceFile(), 'foo', './file_1', 'bar'); |
| if (ic1) { |
| changes.push(ic1); |
| } |
| const ic2 = |
| maybeAddNamedImport(n.getSourceFile(), 'foo2', './file_2', 'bar2'); |
| if (ic2) { |
| changes.push(ic2); |
| } |
| return changes.length ? {changes} : undefined; |
| } |
| }; |
| |
| it('maybeAddNamedImport additions', () => { |
| const results = compileAndCheck( |
| new ConformancePatternRule(baseConfig, addNamedImportFixer), source); |
| |
| expect(results[0]).toHaveFixMatching([ |
| { |
| start: 0, |
| end: 0, |
| replacement: `import {foo as bar} from './file_1';\n` |
| }, |
| { |
| start: 0, |
| end: 0, |
| replacement: `import {foo2 as bar2} from './file_2';\n` |
| } |
| ]); |
| expect(results[0].fixToReadableStringInContext()) |
| .toBe( |
| `Suggested fix:\n` + |
| `- Add new import: import {foo as bar} from './file_1';\n` + |
| `- Add new import: import {foo2 as bar2} from './file_2';`); |
| }); |
| |
| it('maybeAddNamedImport already there', () => { |
| const results = compileAndCheck( |
| new ConformancePatternRule(baseConfig, addNamedImportFixer), |
| 'import {foo as bar} from \'./file_1\';\n' + source, |
| 'export const foo = 1;'); |
| |
| expect(results[0]).toHaveFixMatching([{ |
| start: 37, |
| end: 37, |
| replacement: `import {foo2 as bar2} from './file_2';\n` |
| }]); |
| expect(results[0].fixToReadableStringInContext()) |
| .toBe( |
| `Suggested fix:\n` + |
| `- Add new import: import {foo2 as bar2} from './file_2';`); |
| }); |
| |
| it('maybeAddNamedImport different name', () => { |
| const results = compileAndCheck( |
| new ConformancePatternRule(baseConfig, addNamedImportFixer), |
| 'import {foo as baz} from \'./file_1\';\n' + source, |
| 'export const foo = 1;'); |
| |
| expect(results[0]).toHaveFixMatching([ |
| {start: 8, end: 8, replacement: `foo as bar, `}, { |
| start: 37, |
| end: 37, |
| replacement: `import {foo2 as bar2} from './file_2';\n` |
| } |
| ]); |
| expect(results[0].fixToReadableStringInContext()) |
| .toBe( |
| `Suggested fix:\n` + |
| `- Insert at line 1, char 9: foo as bar,\n` + |
| `- Add new import: import {foo2 as bar2} from './file_2';`); |
| }); |
| |
| it('maybeAddNamespacedImport', () => { |
| const addNamespacedImportFixer: Fixer = { |
| getFixForFlaggedNode(n: ts.Node): Fix | |
| undefined { |
| const ic = |
| maybeAddNamespaceImport(n.getSourceFile(), './file_1', 'foo'); |
| if (ic) return {changes: [ic]}; |
| return; |
| } |
| }; |
| const results = compileAndCheck( |
| new ConformancePatternRule(baseConfig, addNamespacedImportFixer), |
| source); |
| |
| expect(results[0]).toHaveFixMatching([ |
| {start: 0, end: 0, replacement: `import * as foo from './file_1';\n`} |
| ]); |
| }); |
| }); |
| |
| describe('the logic for location->text transforms', () => { |
| const sourceFile = compile(`let a;\nlet b;\n`) |
| .getSourceFiles() |
| .filter(f => f.fileName.indexOf('file_0') !== -1)[0]; |
| // let a;\nlet b;\n |
| // 0123456 7890123 Positions |
| // 1234567 1234567 Expected result in characters |
| |
| it('stringifies as expected', () => { |
| // Only the sourceFile matters here. |
| const failure = new Failure(sourceFile, NaN, NaN, 'whatever', NaN); |
| |
| expect(failure.readableRange(0, 0)).toBe('at line 1, char 1'); |
| expect(failure.readableRange(1, 1)).toBe('at line 1, char 2'); |
| expect(failure.readableRange(0, 1)).toBe('line 1, from char 1 to 2'); |
| expect(failure.readableRange(0, 1)).toBe('line 1, from char 1 to 2'); |
| expect(failure.readableRange(7, 7)).toBe('at line 2, char 1'); |
| expect(failure.readableRange(0, 7)) |
| .toBe('from line 1, char 1 to line 2, char 1'); |
| }); |
| }); |
| }); |
| |
| |
| |
| beforeEach(() => { |
| jasmine.addMatchers(customMatchers); |
| }); |