Skip to content

Regression test for #19328 #19599

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

Closed
wants to merge 2 commits into from
Closed
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
10 changes: 6 additions & 4 deletions src/harness/unittests/extractConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -264,11 +264,13 @@ namespace N { // Force this test to be TS-only
}`);
});

function testExtractConstant(caption: string, text: string) {
testExtractSymbol(caption, text, "extractConstant", Diagnostics.Extract_constant);
function testExtractConstant(caption: string, text: string | { [index: string]: string }) {
if (typeof text === "string") text = { "a": text };
testExtractSymbol(caption, createMapFromTemplate(text), "extractConstant", Diagnostics.Extract_constant);
}

function testExtractConstantFailed(caption: string, text: string) {
testExtractSymbolFailed(caption, text, Diagnostics.Extract_constant);
function testExtractConstantFailed(caption: string, text: string | { [index: string]: string }) {
if (typeof text === "string") text = { "a": text };
testExtractSymbolFailed(caption, createMapFromTemplate(text), Diagnostics.Extract_constant);
}
}
25 changes: 23 additions & 2 deletions src/harness/unittests/extractFunctions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -546,9 +546,30 @@ var q = /*b*/ //c
/*g*/ + /*h*/ //i
/*j*/ 2|] /*k*/ //l
/*m*/; /*n*/ //o`);

// https://github.com/Microsoft/TypeScript/issues/19328
testExtractFunction("extractFunction_importedGenericType", {
"someTypes": `
export type Dictionary<T> = {
[key: string]: T;
};

export type A = string | string[];

export type B = Dictionary<A>;`,
"index": `
import { A, B, Dictionary} from './someTypes';

function doThing(): void {
const aBunchOfA: Dictionary<B> = {};

[#|aBunchOfA['key'] = { a: 'thing' };|]
}`
});
});

function testExtractFunction(caption: string, text: string, includeLib?: boolean) {
testExtractSymbol(caption, text, "extractFunction", Diagnostics.Extract_function, includeLib);
function testExtractFunction(caption: string, text: string | { [index: string]: string }, includeLib?: boolean) {
if (typeof text === "string") text = { "a": text };
testExtractSymbol(caption, createMapFromTemplate(text), "extractFunction", Diagnostics.Extract_function, includeLib);
}
}
16 changes: 9 additions & 7 deletions src/harness/unittests/extractRanges.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
namespace ts {
function testExtractRangeFailed(caption: string, s: string, expectedErrors: string[]) {
return it(caption, () => {
const t = extractTest(s);
const file = createSourceFile("a.ts", t.source, ScriptTarget.Latest, /*setParentNodes*/ true);
const selectionRange = t.ranges.get("selection");
const t = extractTest(createMapFromTemplate({ "a": s }));
const files = arrayFrom(t.files.values());
const file = createSourceFile("a.ts", files[0].source, ScriptTarget.Latest, /*setParentNodes*/ true);
const selectionRange = files[0].ranges.get("selection");
if (!selectionRange) {
throw new Error(`Test ${s} does not specify selection range`);
}
Expand All @@ -17,14 +18,15 @@ namespace ts {
}

function testExtractRange(s: string): void {
const t = extractTest(s);
const f = createSourceFile("a.ts", t.source, ScriptTarget.Latest, /*setParentNodes*/ true);
const selectionRange = t.ranges.get("selection");
const t = extractTest(createMapFromTemplate({ "a": s }));
const files = arrayFrom(t.files.values());
const f = createSourceFile("a.ts", files[0].source, ScriptTarget.Latest, /*setParentNodes*/ true);
const selectionRange = files[0].ranges.get("selection");
if (!selectionRange) {
throw new Error(`Test ${s} does not specify selection range`);
}
const result = refactor.extractSymbol.getRangeToExtract(f, createTextSpanFromBounds(selectionRange.start, selectionRange.end));
const expectedRange = t.ranges.get("extracted");
const expectedRange = files[0].ranges.get("extracted");
if (expectedRange) {
let start: number, end: number;
if (ts.isArray(result.targetRange.range)) {
Expand Down
144 changes: 77 additions & 67 deletions src/harness/unittests/extractTestHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,61 +9,68 @@ namespace ts {
}

export interface Test {
source: string;
ranges: Map<Range>;
files: Map<{
source: string;
ranges: Map<Range>;
}>;
}

export function extractTest(source: string): Test {
const activeRanges: Range[] = [];
let text = "";
let lastPos = 0;
let pos = 0;
const ranges = createMap<Range>();

while (pos < source.length) {
if (source.charCodeAt(pos) === CharacterCodes.openBracket &&
(source.charCodeAt(pos + 1) === CharacterCodes.hash || source.charCodeAt(pos + 1) === CharacterCodes.$)) {
const saved = pos;
pos += 2;
const s = pos;
consumeIdentifier();
const e = pos;
if (source.charCodeAt(pos) === CharacterCodes.bar) {
export function extractTest(sources: Map<string>): Test {
const files: Test["files"] = createMap();
const entries = sources.entries();
for (let {done, value} = entries.next(); !done; { done, value } = entries.next()) {
const [ filename, source ] = value;
const activeRanges: Range[] = [];
let text = "";
let lastPos = 0;
let pos = 0;
const ranges = createMap<Range>();
const consumeIdentifier = () => {
while (isIdentifierPart(source.charCodeAt(pos), ScriptTarget.Latest)) {
pos++;
text += source.substring(lastPos, saved);
const name = s === e
? source.charCodeAt(saved + 1) === CharacterCodes.hash ? "selection" : "extracted"
: source.substring(s, e);
activeRanges.push({ name, start: text.length, end: undefined });
lastPos = pos;
continue;
}
else {
pos = saved;
};

while (pos < source.length) {
if (source.charCodeAt(pos) === CharacterCodes.openBracket &&
(source.charCodeAt(pos + 1) === CharacterCodes.hash || source.charCodeAt(pos + 1) === CharacterCodes.$)) {
const saved = pos;
pos += 2;
const s = pos;
consumeIdentifier();
const e = pos;
if (source.charCodeAt(pos) === CharacterCodes.bar) {
pos++;
text += source.substring(lastPos, saved);
const name = s === e
? source.charCodeAt(saved + 1) === CharacterCodes.hash ? "selection" : "extracted"
: source.substring(s, e);
activeRanges.push({ name, start: text.length, end: undefined });
lastPos = pos;
continue;
}
else {
pos = saved;
}
}
}
else if (source.charCodeAt(pos) === CharacterCodes.bar && source.charCodeAt(pos + 1) === CharacterCodes.closeBracket) {
text += source.substring(lastPos, pos);
activeRanges[activeRanges.length - 1].end = text.length;
const range = activeRanges.pop();
if (range.name in ranges) {
throw new Error(`Duplicate name of range ${range.name}`);
else if (source.charCodeAt(pos) === CharacterCodes.bar && source.charCodeAt(pos + 1) === CharacterCodes.closeBracket) {
text += source.substring(lastPos, pos);
activeRanges[activeRanges.length - 1].end = text.length;
const range = activeRanges.pop();
if (range.name in ranges) {
throw new Error(`Duplicate name of range ${range.name}`);
}
ranges.set(range.name, range);
pos += 2;
lastPos = pos;
continue;
}
ranges.set(range.name, range);
pos += 2;
lastPos = pos;
continue;
}
pos++;
}
text += source.substring(lastPos, pos);

function consumeIdentifier() {
while (isIdentifierPart(source.charCodeAt(pos), ScriptTarget.Latest)) {
pos++;
}
text += source.substring(lastPos, pos);
files.set(filename, { source: text, ranges });
}
return { source: text, ranges };
return { files };
}

export const newLineCharacter = "\n";
Expand Down Expand Up @@ -104,19 +111,22 @@ namespace ts {
getCurrentDirectory: notImplemented,
};

export function testExtractSymbol(caption: string, text: string, baselineFolder: string, description: DiagnosticMessage, includeLib?: boolean) {
const t = extractTest(text);
const selectionRange = t.ranges.get("selection");
if (!selectionRange) {
export function testExtractSymbol(caption: string, files: Map<string>, baselineFolder: string, description: DiagnosticMessage, includeLib?: boolean) {
const t = extractTest(files);
const fsentries = arrayFrom(t.files.entries());
const selectionRangeCandidates = filter(fsentries, t => !!t[1].ranges.get("selection"));
if (!selectionRangeCandidates || !selectionRangeCandidates.length) {
throw new Error(`Test ${caption} does not specify selection range`);
}
const selectionRange = selectionRangeCandidates[0][1].ranges.get("selection");

[Extension.Ts, Extension.Js].forEach(extension =>
it(`${caption} [${extension}]`, () => runBaseline(extension)));

function runBaseline(extension: Extension) {
const path = "/a" + extension;
const program = makeProgram({ path, content: t.source }, includeLib);
const path = `/${selectionRangeCandidates[0][0]}${extension}`;
const text = files.get(selectionRangeCandidates[0][0]);
const program = makeProgram(map(fsentries, ([name, t]) => ({ path: `/${name}${extension}`, content: t.source })), includeLib);

if (hasSyntacticDiagnostics(program)) {
// Don't bother generating JS baselines for inputs that aren't valid JS.
Expand Down Expand Up @@ -152,17 +162,17 @@ namespace ts {
const newTextWithRename = newText.slice(0, renameLocation) + "/*RENAME*/" + newText.slice(renameLocation);
data.push(newTextWithRename);

const diagProgram = makeProgram({ path, content: newText }, includeLib);
const diagProgram = makeProgram(map(fsentries, ([name, t]) => (`/${name}${extension}` === path ? { path, content: newText } : { path: `/${name}${extension}`, content: t.source })), includeLib);
assert.isFalse(hasSyntacticDiagnostics(diagProgram));
}
return data.join(newLineCharacter);
});
}

function makeProgram(f: {path: string, content: string }, includeLib?: boolean) {
const host = projectSystem.createServerHost(includeLib ? [f, projectSystem.libFile] : [f]); // libFile is expensive to parse repeatedly - only test when required
function makeProgram(f: {path: string, content: string }[], includeLib?: boolean) {
const host = projectSystem.createServerHost(includeLib ? [...f, projectSystem.libFile] : f); // libFile is expensive to parse repeatedly - only test when required
const projectService = projectSystem.createProjectService(host);
projectService.openClientFile(f.path);
projectService.openClientFile(f[f.length - 1].path);
const program = projectService.inferredProjects[0].getLanguageService().getProgram();
return program;
}
Expand All @@ -173,22 +183,22 @@ namespace ts {
}
}

export function testExtractSymbolFailed(caption: string, text: string, description: DiagnosticMessage) {
export function testExtractSymbolFailed(caption: string, files: Map<string>, description: DiagnosticMessage) {
it(caption, () => {
const t = extractTest(text);
const selectionRange = t.ranges.get("selection");
if (!selectionRange) {
const t = extractTest(files);
const fsentries = arrayFrom(t.files.entries());
const selectionRangeCandidates = filter(fsentries, t => !!t[1].ranges.get("selection"));
if (!selectionRangeCandidates || !selectionRangeCandidates.length) {
throw new Error(`Test ${caption} does not specify selection range`);
}
const f = {
path: "/a.ts",
content: t.source
};
const host = projectSystem.createServerHost([f, projectSystem.libFile]);
const selectionRange = selectionRangeCandidates[0][1].ranges.get("selection");
const testPath = `/${selectionRangeCandidates[0][0]}.ts`;

const host = projectSystem.createServerHost([...map(fsentries, ([name, t]) => ({ path: `/${name}.ts`, content: t.source })), projectSystem.libFile]);
const projectService = projectSystem.createProjectService(host);
projectService.openClientFile(f.path);
projectService.openClientFile(testPath);
const program = projectService.inferredProjects[0].getLanguageService().getProgram();
const sourceFile = program.getSourceFile(f.path);
const sourceFile = program.getSourceFile(testPath);
const context: RefactorContext = {
cancellationToken: { throwIfCancellationRequested() { }, isCancellationRequested() { return false; } },
newLineCharacter,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// ==ORIGINAL==

import { A, B, Dictionary} from './someTypes';

function doThing(): void {
const aBunchOfA: Dictionary<B> = {};

/*[#|*/aBunchOfA['key'] = { a: 'thing' };/*|]*/
}
// ==SCOPE::Extract to inner function in function 'doThing'==

import { A, B, Dictionary} from './someTypes';

function doThing(): void {
const aBunchOfA: Dictionary<B> = {};

/*RENAME*/newFunction();

function newFunction() {
aBunchOfA['key'] = { a: 'thing' };
}
}
// ==SCOPE::Extract to function in module scope==

import { A, B, Dictionary} from './someTypes';

function doThing(): void {
const aBunchOfA: Dictionary<B> = {};

/*RENAME*/newFunction(aBunchOfA);
}

function newFunction(aBunchOfA: Dictionary<Dictionary<A>>) {
aBunchOfA['key'] = { a: 'thing' };
}