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");
            if (!selectionRange) {
                throw new Error(`Test ${s} does not specify selection range`);
            }
            const result = refactor.extractSymbol.getRangeToExtract(file, createTextSpanFromRange(selectionRange), /*userRequested*/ false);
            assert(result.targetRange === undefined, "failure expected");
            const sortedErrors = result.errors.map(e => e.messageText as string).sort();
            assert.deepEqual(sortedErrors, expectedErrors.sort(), "unexpected errors");
        });
    }

    function testExtractRange(caption: string, s: string) {
        return it(caption, () => {
            const t = extractTest(s);
            const f = createSourceFile("a.ts", t.source, ScriptTarget.Latest, /*setParentNodes*/ true);
            const selectionRange = t.ranges.get("selection");
            if (!selectionRange) {
                throw new Error(`Test ${s} does not specify selection range`);
            }
            const result = refactor.extractSymbol.getRangeToExtract(f, createTextSpanFromRange(selectionRange));
            const expectedRange = t.ranges.get("extracted");
            if (expectedRange) {
                let pos: number, end: number;
                const targetRange = result.targetRange!;
                if (isArray(targetRange.range)) {
                    pos = targetRange.range[0].getStart(f);
                    end = last(targetRange.range).getEnd();
                }
                else {
                    pos = targetRange.range.getStart(f);
                    end = targetRange.range.getEnd();
                }
                assert.equal(pos, expectedRange.pos, "incorrect pos of range");
                assert.equal(end, expectedRange.end, "incorrect end of range");
            }
            else {
                assert.isTrue(!result.targetRange, `expected range to extract to be undefined`);
            }
        });
    }

    describe("unittests:: services:: extract:: extractRanges", () => {
        describe("get extract range from selection", () => {
            testExtractRange("extractRange1", `
                [#|
                [$|var x = 1;
                var y = 2;|]|]
            `);
            testExtractRange("extractRange2", `
                [$|[#|var x = 1;
                var y = 2|];|]
            `);
            testExtractRange("extractRange3", `
                [#|var x = [$|1|]|];
                var y = 2;
            `);
            testExtractRange("extractRange4", `
                var x = [$|10[#|00|]|];
            `);
            testExtractRange("extractRange5", `
                [$|va[#|r foo = 1;
                var y = 200|]0;|]
            `);
            testExtractRange("extractRange6", `
                var x = [$|fo[#|o.bar.baz()|]|];
            `);
            testExtractRange("extractRange7", `
                if ([#|[#extracted|a && b && c && d|]|]) {
                }
            `);
            testExtractRange("extractRange8", `
                if [#|(a && b && c && d|]) {
                }
            `);
            testExtractRange("extractRange9", `
                if ([$|a[#|a && b && c && d|]d|]) {
                }
            `);
            testExtractRange("extractRange10", `
                if (a && b && c && d) {
                [#|    [$|var x = 1;
                    console.log(x);|]    |]
                }
            `);
            testExtractRange("extractRange11", `
                [#|
                if (a) {
                    return 100;
                } |]
            `);
            testExtractRange("extractRange12", `
                function foo() {
                [#|    [$|if (a) {
                    }
                    return 100|] |]
                }
            `);
            testExtractRange("extractRange13", `
                [#|
                [$|l1:
                if (x) {
                    break l1;
                }|]|]
            `);
            testExtractRange("extractRange14", `
                [#|
                [$|l2:
                {
                    if (x) {
                    }
                    break l2;
                }|]|]
            `);
            testExtractRange("extractRange15", `
                while (true) {
                [#|    if(x) {
                    }
                    break;  |]
                }
            `);
            testExtractRange("extractRange16", `
                while (true) {
                [#|    if(x) {
                    }
                    continue;  |]
                }
            `);
            testExtractRange("extractRange17", `
                l3:
                {
                    [#|
                    if (x) {
                    }
                    break l3; |]
                }
            `);
            testExtractRange("extractRange18", `
                function f() {
                    while (true) {
                [#|
                        if (x) {
                            return;
                        } |]
                    }
                }
            `);
            testExtractRange("extractRange19", `
                function f() {
                    while (true) {
                [#|
                        [$|if (x) {
                        }
                        return;|]
                |]
                    }
                }
            `);
            testExtractRange("extractRange20", `
                function f() {
                    return [#|  [$|1 + 2|]  |]+ 3;
                    }
                }
            `);
            testExtractRange("extractRange21", `
                function f(x: number) {
                    [#|[$|try {
                        x++;
                    }
                    finally {
                        return 1;
                    }|]|]
                }
            `);

            // Variable statements
            testExtractRange("extractRange22", `[#|let x = [$|1|];|]`);
            testExtractRange("extractRange23", `[#|let x = [$|1|], y;|]`);
            testExtractRange("extractRange24", `[#|[$|let x = 1, y = 1;|]|]`);

            // Variable declarations
            testExtractRange("extractRange25", `let [#|x = [$|1|]|];`);
            testExtractRange("extractRange26", `let [#|x = [$|1|]|], y = 2;`);
            testExtractRange("extractRange27", `let x = 1, [#|y = [$|2|]|];`);

            // Return statements
            testExtractRange("extractRange28", `[#|return [$|1|];|]`);

            // For statements
            testExtractRange("extractRange29", `for ([#|var i = [$|1|]|]; i < 2; i++) {}`);
            testExtractRange("extractRange30", `for (var i = [#|[$|1|]|]; i < 2; i++) {}`);
        });

        testExtractRangeFailed("extractRangeFailed1",
            `
namespace A {
function f() {
    [#|
    let x = 1
    if (x) {
        return 10;
    }
    |]
}
}
            `,
            [refactor.extractSymbol.Messages.cannotExtractRangeContainingConditionalReturnStatement.message]);

        testExtractRangeFailed("extractRangeFailed2",
            `
namespace A {
function f() {
    while (true) {
    [#|
        let x = 1
        if (x) {
            break;
        }
    |]
    }
}
}
            `,
            [refactor.extractSymbol.Messages.cannotExtractRangeContainingConditionalBreakOrContinueStatements.message]);

        testExtractRangeFailed("extractRangeFailed3",
            `
namespace A {
function f() {
    while (true) {
    [#|
        let x = 1
        if (x) {
            continue;
        }
    |]
    }
}
}
            `,
            [refactor.extractSymbol.Messages.cannotExtractRangeContainingConditionalBreakOrContinueStatements.message]);

        testExtractRangeFailed("extractRangeFailed4",
            `
namespace A {
function f() {
    l1: {
    [#|
        let x = 1
        if (x) {
            break l1;
        }
    |]
    }
}
}
            `,
            [refactor.extractSymbol.Messages.cannotExtractRangeContainingLabeledBreakOrContinueStatementWithTargetOutsideOfTheRange.message]);

        testExtractRangeFailed("extractRangeFailed5",
            `
namespace A {
function f() {
    [#|
    try {
        f2()
        return 10;
    }
    catch (e) {
    }
    |]
}
function f2() {
}
}
            `,
            [refactor.extractSymbol.Messages.cannotExtractRangeContainingConditionalReturnStatement.message]);

        testExtractRangeFailed("extractRangeFailed6",
            `
namespace A {
function f() {
    [#|
    try {
        f2()
    }
    catch (e) {
        return 10;
    }
    |]
}
function f2() {
}
}
            `,
            [refactor.extractSymbol.Messages.cannotExtractRangeContainingConditionalReturnStatement.message]);

        testExtractRangeFailed("extractRangeFailed7",
            `
function test(x: number) {
while (x) {
    x--;
    [#|break;|]
}
}
            `,
            [refactor.extractSymbol.Messages.cannotExtractRangeContainingConditionalBreakOrContinueStatements.message]);

        testExtractRangeFailed("extractRangeFailed8",
            `
function test(x: number) {
switch (x) {
    case 1:
        [#|break;|]
}
}
            `,
            [refactor.extractSymbol.Messages.cannotExtractRangeContainingConditionalBreakOrContinueStatements.message]);

        testExtractRangeFailed("extractRangeFailed9",
            `var x = ([#||]1 + 2);`,
            [refactor.extractSymbol.Messages.cannotExtractEmpty.message]);

        testExtractRangeFailed("extractRangeFailed10",
            `
                function f() {
                    return 1 + [#|2 + 3|];
                    }
                }
            `,
            [refactor.extractSymbol.Messages.cannotExtractRange.message]);

        testExtractRangeFailed("extractRangeFailed11",
            `
                function f(x: number) {
                    while (true) {
                        [#|try {
                            x++;
                        }
                        finally {
                            break;
                        }|]
                    }
                }
            `,
            [refactor.extractSymbol.Messages.cannotExtractRangeContainingConditionalBreakOrContinueStatements.message]);

        testExtractRangeFailed("extractRangeFailed12",
            `let [#|x|];`,
            [refactor.extractSymbol.Messages.statementOrExpressionExpected.message]);

        testExtractRangeFailed("extractRangeFailed13",
            `[#|return;|]`,
            [refactor.extractSymbol.Messages.cannotExtractRange.message]);

        testExtractRangeFailed("extractRangeFailed14",
            `
                switch(1) {
                    case [#|1:
                        break;|]
                }
            `,
            [refactor.extractSymbol.Messages.cannotExtractRange.message]);

        testExtractRangeFailed("extractRangeFailed15",
            `
                switch(1) {
                    case [#|1:
                        break|];
                }
            `,
            [refactor.extractSymbol.Messages.cannotExtractRange.message]);

        // Documentation only - it would be nice if the result were [$|1|]
        testExtractRangeFailed("extractRangeFailed16",
            `
                switch(1) {
                    [#|case 1|]:
                        break;
                }
            `,
            [refactor.extractSymbol.Messages.cannotExtractRange.message]);

        // Documentation only - it would be nice if the result were [$|1|]
        testExtractRangeFailed("extractRangeFailed17",
            `
                switch(1) {
                    [#|case 1:|]
                        break;
                }
            `,
            [refactor.extractSymbol.Messages.cannotExtractRange.message]);

        testExtractRangeFailed("extractRangeFailed18",
            `[#|{ 1;|] }`,
            [refactor.extractSymbol.Messages.cannotExtractRange.message]);

        testExtractRangeFailed("extractRangeFailed19",
            `[#|/** @type {number} */|] const foo = 1;`,
            [refactor.extractSymbol.Messages.cannotExtractJSDoc.message]);

        testExtractRangeFailed("extract-method-not-for-token-expression-statement", `[#|a|]`, [refactor.extractSymbol.Messages.cannotExtractIdentifier.message]);
    });
}
