Merge branch 'master' into excessPropCorrection

This commit is contained in:
Daniel Rosenwasser 2017-08-22 12:10:16 -07:00 committed by GitHub
commit 40e9e85cb3
48 changed files with 914 additions and 274 deletions

View File

@ -16,4 +16,5 @@ Jakefile.js
.gitattributes
.settings/
.travis.yml
.vscode/
.vscode/
test.config

View File

@ -16,6 +16,7 @@ matrix:
branches:
only:
- master
- release-2.5
install:
- npm uninstall typescript --no-save

View File

@ -17,6 +17,6 @@ nodeVersions.each { nodeVer ->
}
Utilities.standardJobSetup(newJob, project, true, "*/${branch}")
Utilities.setMachineAffinity(newJob, 'Ubuntu', '20161020')
Utilities.setMachineAffinity(newJob, 'Ubuntu14.04', '20170821-1')
Utilities.addGithubPRTriggerForBranch(newJob, branch, "TypeScript Test Run ${newJobName}")
}

View File

@ -7,11 +7,15 @@ function endsWith(s: string, suffix: string) {
return s.lastIndexOf(suffix, s.length - suffix.length) !== -1;
}
function isStringEnum(declaration: ts.EnumDeclaration) {
return declaration.members.length && declaration.members.every(m => m.initializer && m.initializer.kind === ts.SyntaxKind.StringLiteral);
}
class DeclarationsWalker {
private visitedTypes: ts.Type[] = [];
private text = "";
private removedTypes: ts.Type[] = [];
private constructor(private typeChecker: ts.TypeChecker, private protocolFile: ts.SourceFile) {
}
@ -19,7 +23,7 @@ class DeclarationsWalker {
let text = "declare namespace ts.server.protocol {\n";
var walker = new DeclarationsWalker(typeChecker, protocolFile);
walker.visitTypeNodes(protocolFile);
text = walker.text
text = walker.text
? `declare namespace ts.server.protocol {\n${walker.text}}`
: "";
if (walker.removedTypes) {
@ -52,7 +56,7 @@ class DeclarationsWalker {
if (sourceFile === this.protocolFile || path.basename(sourceFile.fileName) === "lib.d.ts") {
return;
}
if (decl.kind === ts.SyntaxKind.EnumDeclaration) {
if (decl.kind === ts.SyntaxKind.EnumDeclaration && !isStringEnum(decl as ts.EnumDeclaration)) {
this.removedTypes.push(type);
return;
}
@ -91,7 +95,7 @@ class DeclarationsWalker {
for (const type of heritageClauses[0].types) {
this.processTypeOfNode(type);
}
}
}
break;
}
}
@ -110,7 +114,7 @@ class DeclarationsWalker {
this.processType(type);
}
}
}
}
}
function writeProtocolFile(outputFile: string, protocolTs: string, typeScriptServicesDts: string) {

View File

@ -1016,7 +1016,18 @@ namespace ts {
}
}
break;
case SyntaxKind.ExpressionWithTypeArguments:
// The type parameters of a class are not in scope in the base class expression.
if (lastLocation === (<ExpressionWithTypeArguments>location).expression && (<HeritageClause>location.parent).token === SyntaxKind.ExtendsKeyword) {
const container = location.parent.parent;
if (isClassLike(container) && (result = lookup(getSymbolOfNode(container).members, name, meaning & SymbolFlags.Type))) {
if (nameNotFoundMessage) {
error(errorLocation, Diagnostics.Base_class_expressions_cannot_reference_class_type_parameters);
}
return undefined;
}
}
break;
// It is not legal to reference a class's own type parameters from a computed property name that
// belongs to the class. For example:
//
@ -4929,7 +4940,7 @@ namespace ts {
}
function resolveBaseTypesOfClass(type: InterfaceType): void {
type.resolvedBaseTypes = type.resolvedBaseTypes || emptyArray;
type.resolvedBaseTypes = emptyArray;
const baseConstructorType = getApparentType(getBaseConstructorTypeOfClass(type));
if (!(baseConstructorType.flags & (TypeFlags.Object | TypeFlags.Intersection | TypeFlags.Any))) {
return;
@ -4976,17 +4987,12 @@ namespace ts {
error(baseTypeNode.expression, Diagnostics.Base_constructor_return_type_0_is_not_a_class_or_interface_type, typeToString(baseType));
return;
}
if (type === baseType || hasBaseType(<BaseType>baseType, type)) {
if (type === baseType || hasBaseType(baseType, type)) {
error(valueDecl, Diagnostics.Type_0_recursively_references_itself_as_a_base_type,
typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.WriteArrayAsGenericType));
return;
}
if (type.resolvedBaseTypes === emptyArray) {
type.resolvedBaseTypes = [<ObjectType>baseType];
}
else {
type.resolvedBaseTypes.push(<ObjectType>baseType);
}
type.resolvedBaseTypes = [baseType];
}
function areAllOuterTypeParametersApplied(type: Type): boolean {
@ -5003,7 +5009,7 @@ namespace ts {
// A valid base type is `any`, any non-generic object type or intersection of non-generic
// object types.
function isValidBaseType(type: Type): boolean {
function isValidBaseType(type: Type): type is BaseType {
return type.flags & (TypeFlags.Object | TypeFlags.NonPrimitive | TypeFlags.Any) && !isGenericMappedType(type) ||
type.flags & TypeFlags.Intersection && !forEach((<IntersectionType>type).types, t => !isValidBaseType(t));
}
@ -5016,12 +5022,12 @@ namespace ts {
const baseType = getTypeFromTypeNode(node);
if (baseType !== unknownType) {
if (isValidBaseType(baseType)) {
if (type !== baseType && !hasBaseType(<BaseType>baseType, type)) {
if (type !== baseType && !hasBaseType(baseType, type)) {
if (type.resolvedBaseTypes === emptyArray) {
type.resolvedBaseTypes = [<ObjectType>baseType];
}
else {
type.resolvedBaseTypes.push(<ObjectType>baseType);
type.resolvedBaseTypes.push(baseType);
}
}
else {
@ -5657,17 +5663,12 @@ namespace ts {
else {
// Combinations of function, class, enum and module
let members = emptySymbols;
let constructSignatures: Signature[] = emptyArray;
let stringIndexInfo: IndexInfo = undefined;
if (symbol.exports) {
members = getExportsOfSymbol(symbol);
}
if (symbol.flags & SymbolFlags.Class) {
const classType = getDeclaredTypeOfClassOrInterface(symbol);
constructSignatures = getSignaturesOfSymbol(symbol.members.get(InternalSymbolName.Constructor));
if (!constructSignatures.length) {
constructSignatures = getDefaultConstructSignatures(classType);
}
const baseConstructorType = getBaseConstructorTypeOfClass(classType);
if (baseConstructorType.flags & (TypeFlags.Object | TypeFlags.Intersection | TypeFlags.TypeVariable)) {
members = createSymbolTable(getNamedMembers(members));
@ -5678,7 +5679,7 @@ namespace ts {
}
}
const numberIndexInfo = symbol.flags & SymbolFlags.Enum ? enumNumberIndexInfo : undefined;
setStructuredTypeMembers(type, members, emptyArray, constructSignatures, stringIndexInfo, numberIndexInfo);
setStructuredTypeMembers(type, members, emptyArray, emptyArray, stringIndexInfo, numberIndexInfo);
// We resolve the members before computing the signatures because a signature may use
// typeof with a qualified name expression that circularly references the type we are
// in the process of resolving (see issue #6072). The temporarily empty signature list
@ -5686,6 +5687,15 @@ namespace ts {
if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method)) {
(<ResolvedType>type).callSignatures = getSignaturesOfSymbol(symbol);
}
// And likewise for construct signatures for classes
if (symbol.flags & SymbolFlags.Class) {
const classType = getDeclaredTypeOfClassOrInterface(symbol);
let constructSignatures = getSignaturesOfSymbol(symbol.members.get(InternalSymbolName.Constructor));
if (!constructSignatures.length) {
constructSignatures = getDefaultConstructSignatures(classType);
}
(<ResolvedType>type).constructSignatures = constructSignatures;
}
}
}

View File

@ -1916,6 +1916,10 @@
"category": "Error",
"code": 2561
},
"Base class expressions cannot reference class type parameters.": {
"category": "Error",
"code": 2562
},
"JSX element attributes type '{0}' may not be a union type.": {
"category": "Error",
"code": 2600

View File

@ -647,7 +647,7 @@ namespace ts {
}
export function getLeadingCommentRangesOfNode(node: Node, sourceFileOfNode: SourceFile) {
return getLeadingCommentRanges(sourceFileOfNode.text, node.pos);
return node.kind !== SyntaxKind.JsxText ? getLeadingCommentRanges(sourceFileOfNode.text, node.pos) : undefined;
}
export function getJSDocCommentRanges(node: Node, text: string) {

View File

@ -2508,6 +2508,23 @@ namespace FourSlash {
}
}
public verifySpanOfEnclosingComment(negative: boolean, onlyMultiLineDiverges?: boolean) {
const expected = !negative;
const position = this.currentCaretPosition;
const fileName = this.activeFile.fileName;
const actual = !!this.languageService.getSpanOfEnclosingComment(fileName, position, /*onlyMultiLine*/ false);
const actualOnlyMultiLine = !!this.languageService.getSpanOfEnclosingComment(fileName, position, /*onlyMultiLine*/ true);
if (expected !== actual || onlyMultiLineDiverges === (actual === actualOnlyMultiLine)) {
this.raiseError(`verifySpanOfEnclosingComment failed:
position: '${position}'
fileName: '${fileName}'
onlyMultiLineDiverges: '${onlyMultiLineDiverges}'
actual: '${actual}'
actualOnlyMultiLine: '${actualOnlyMultiLine}'
expected: '${expected}'.`);
}
}
/*
Check number of navigationItems which match both searchValue and matchKind,
if a filename is passed in, limit the results to that file.
@ -3648,6 +3665,10 @@ namespace FourSlashInterface {
this.state.verifyBraceCompletionAtPosition(this.negative, openingBrace);
}
public isInCommentAtPosition(onlyMultiLineDiverges?: boolean) {
this.state.verifySpanOfEnclosingComment(this.negative, onlyMultiLineDiverges);
}
public codeFixAvailable() {
this.state.verifyCodeFixAvailable(this.negative);
}

View File

@ -487,6 +487,9 @@ namespace Harness.LanguageService {
isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean {
return unwrapJSONCallResult(this.shim.isValidBraceCompletionAtPosition(fileName, position, openingBrace));
}
getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): ts.TextSpan {
return unwrapJSONCallResult(this.shim.getSpanOfEnclosingComment(fileName, position, onlyMultiLine));
}
getCodeFixesAtPosition(): ts.CodeAction[] {
throw new Error("Not supported on the shim.");
}
@ -686,7 +689,7 @@ namespace Harness.LanguageService {
this.host.log(message);
}
err(message: string): void {
msg(message: string): void {
this.host.log(message);
}
@ -702,7 +705,8 @@ namespace Harness.LanguageService {
return false;
}
group() { throw ts.notImplemented(); }
startGroup() { throw ts.notImplemented(); }
endGroup() { throw ts.notImplemented(); }
perftrc(message: string): void {
return this.host.log(message);

View File

@ -90,9 +90,16 @@ namespace RWC {
ts.setConfigFileInOptions(opts.options, configParseResult.options.configFile);
}
// Load the files
// Deduplicate files so they are only printed once in baselines (they are deduplicated within the compiler already)
const uniqueNames = ts.createMap<true>();
for (const fileName of fileNames) {
inputFiles.push(getHarnessCompilerInputUnit(fileName));
// Must maintain order, build result list while checking map
const normalized = ts.normalizeSlashes(fileName);
if (!uniqueNames.has(normalized)) {
uniqueNames.set(normalized, true);
// Load the file
inputFiles.push(getHarnessCompilerInputUnit(fileName));
}
}
// Add files to compilation

View File

@ -39,8 +39,9 @@ namespace ts.projectSystem {
loggingEnabled: () => false,
perftrc: noop,
info: noop,
err: noop,
group: noop,
msg: noop,
startGroup: noop,
endGroup: noop,
getLogFileName: (): string => undefined
};

View File

@ -527,6 +527,10 @@ namespace ts.server {
return notImplemented();
}
getSpanOfEnclosingComment(_fileName: string, _position: number, _onlyMultiLine: boolean): TextSpan {
return notImplemented();
}
getCodeFixesAtPosition(file: string, start: number, end: number, errorCodes: number[]): CodeAction[] {
const args: protocol.CodeFixRequestArgs = { ...this.createFileRangeRequestArgs(file, start, end), errorCodes };

View File

@ -958,28 +958,28 @@ namespace ts.server {
return;
}
this.logger.group(info => {
let counter = 0;
counter = printProjects(this.externalProjects, info, counter);
counter = printProjects(this.configuredProjects, info, counter);
printProjects(this.inferredProjects, info, counter);
info("Open files: ");
for (const rootFile of this.openFiles) {
info(`\t${rootFile.fileName}`);
}
});
function printProjects(projects: Project[], info: (msg: string) => void, counter: number): number {
this.logger.startGroup();
let counter = 0;
const printProjects = (projects: Project[], counter: number): number => {
for (const project of projects) {
project.updateGraph();
info(`Project '${project.getProjectName()}' (${ProjectKind[project.projectKind]}) ${counter}`);
info(project.filesToString());
info("-----------------------------------------------");
this.logger.info(`Project '${project.getProjectName()}' (${ProjectKind[project.projectKind]}) ${counter}`);
this.logger.info(project.filesToString());
this.logger.info("-----------------------------------------------");
counter++;
}
return counter;
};
counter = printProjects(this.externalProjects, counter);
counter = printProjects(this.configuredProjects, counter);
printProjects(this.inferredProjects, counter);
this.logger.info("Open files: ");
for (const rootFile of this.openFiles) {
this.logger.info(`\t${rootFile.fileName}`);
}
this.logger.endGroup();
}
private findConfiguredProjectByProjectName(configFileName: NormalizedPath) {

View File

@ -8,6 +8,7 @@ namespace ts.server.protocol {
/* @internal */
BraceFull = "brace-full",
BraceCompletion = "braceCompletion",
GetSpanOfEnclosingComment = "getSpanOfEnclosingComment",
Change = "change",
Close = "close",
Completions = "completions",
@ -241,6 +242,21 @@ namespace ts.server.protocol {
body?: TodoComment[];
}
/**
* A request to determine if the caret is inside a comment.
*/
export interface SpanOfEnclosingCommentRequest extends FileLocationRequest {
command: CommandTypes.GetSpanOfEnclosingComment;
arguments: SpanOfEnclosingCommentRequestArgs;
}
export interface SpanOfEnclosingCommentRequestArgs extends FileLocationRequestArgs {
/**
* Requires that the enclosing span be a multi-line comment, or else the request returns undefined.
*/
onlyMultiLine: boolean;
}
/**
* Request to obtain outlining spans in file.
*/
@ -2473,6 +2489,7 @@ namespace ts.server.protocol {
System = "System",
ES6 = "ES6",
ES2015 = "ES2015",
ESNext = "ESNext"
}
export const enum ModuleResolutionKind {
@ -2490,5 +2507,8 @@ namespace ts.server.protocol {
ES5 = "ES5",
ES6 = "ES6",
ES2015 = "ES2015",
ES2016 = "ES2016",
ES2017 = "ES2017",
ESNext = "ESNext"
}
}

View File

@ -140,6 +140,8 @@ namespace ts.server {
class Logger implements server.Logger {
private fd = -1;
private seq = 0;
private inGroup = false;
private firstInGroup = true;
constructor(private readonly logFilename: string,
private readonly traceToConsole: boolean,
@ -169,24 +171,24 @@ namespace ts.server {
}
perftrc(s: string) {
this.msg(s, "Perf");
this.msg(s, Msg.Perf);
}
info(s: string) {
this.msg(s, "Info");
this.msg(s, Msg.Info);
}
err(s: string) {
this.msg(s, "Err");
this.msg(s, Msg.Err);
}
group(logGroupEntries: (log: (msg: string) => void) => void) {
let firstInGroup = false;
logGroupEntries(s => {
this.msg(s, "Info", /*inGroup*/ true, firstInGroup);
firstInGroup = false;
});
this.seq++;
startGroup() {
this.inGroup = true;
this.firstInGroup = true;
}
endGroup() {
this.inGroup = false;
}
loggingEnabled() {
@ -197,16 +199,16 @@ namespace ts.server {
return this.loggingEnabled() && this.level >= level;
}
private msg(s: string, type: string, inGroup = false, firstInGroup = false) {
msg(s: string, type: Msg.Types = Msg.Err) {
if (!this.canWrite) return;
s = `[${nowString()}] ${s}\n`;
if (!inGroup || firstInGroup) {
if (!this.inGroup || this.firstInGroup) {
const prefix = Logger.padStringRight(type + " " + this.seq.toString(), " ");
s = prefix + s;
}
this.write(s);
if (!inGroup) {
if (!this.inGroup) {
this.seq++;
}
}

View File

@ -368,7 +368,7 @@ namespace ts.server {
msg += "\n" + (<StackTraceError>err).stack;
}
}
this.logger.err(msg);
this.logger.msg(msg, Msg.Err);
}
public send(msg: protocol.Message) {
@ -1025,6 +1025,14 @@ namespace ts.server {
return project.getLanguageService(/*ensureSynchronized*/ false).getDocCommentTemplateAtPosition(file, position);
}
private getSpanOfEnclosingComment(args: protocol.SpanOfEnclosingCommentRequestArgs) {
const { file, project } = this.getFileAndProjectWithoutRefreshingInferredProjects(args);
const scriptInfo = project.getScriptInfoForNormalizedPath(file);
const onlyMultiLine = args.onlyMultiLine;
const position = this.getPosition(args, scriptInfo);
return project.getLanguageService(/*ensureSynchronized*/ false).getSpanOfEnclosingComment(file, position, onlyMultiLine);
}
private getIndentation(args: protocol.IndentationRequestArgs) {
const { file, project } = this.getFileAndProjectWithoutRefreshingInferredProjects(args);
const position = this.getPosition(args, project.getScriptInfoForNormalizedPath(file));
@ -1765,6 +1773,9 @@ namespace ts.server {
[CommandNames.DocCommentTemplate]: (request: protocol.DocCommentTemplateRequest) => {
return this.requiredResponse(this.getDocCommentTemplate(request.arguments));
},
[CommandNames.GetSpanOfEnclosingComment]: (request: protocol.SpanOfEnclosingCommentRequest) => {
return this.requiredResponse(this.getSpanOfEnclosingComment(request.arguments));
},
[CommandNames.Format]: (request: protocol.FormatRequest) => {
return this.requiredResponse(this.getFormattingEditsForRange(request.arguments));
},
@ -1947,7 +1958,7 @@ namespace ts.server {
return this.executeWithRequestId(request.seq, () => handler(request));
}
else {
this.logger.err(`Unrecognized JSON command: ${JSON.stringify(request)}`);
this.logger.msg(`Unrecognized JSON command: ${JSON.stringify(request)}`, Msg.Err);
this.output(undefined, CommandNames.Unknown, request.seq, `Unrecognized JSON command: ${request.command}`);
return { responseRequired: false };
}

View File

@ -102,7 +102,7 @@ namespace ts.server.typingsInstaller {
if (this.log.isEnabled()) {
this.log.writeLine(`Updating ${TypesRegistryPackageName} npm package...`);
}
this.execSync(`${this.npmPath} install ${TypesRegistryPackageName}`, { cwd: globalTypingsCacheLocation, stdio: "ignore" });
this.execSync(`${this.npmPath} install --ignore-scripts ${TypesRegistryPackageName}`, { cwd: globalTypingsCacheLocation, stdio: "ignore" });
if (this.log.isEnabled()) {
this.log.writeLine(`Updated ${TypesRegistryPackageName} npm package`);
}
@ -152,7 +152,7 @@ namespace ts.server.typingsInstaller {
if (this.log.isEnabled()) {
this.log.writeLine(`#${requestId} with arguments'${JSON.stringify(args)}'.`);
}
const command = `${this.npmPath} install ${args.join(" ")} --save-dev --user-agent="typesInstaller/${version}"`;
const command = `${this.npmPath} install --ignore-scripts ${args.join(" ")} --save-dev --user-agent="typesInstaller/${version}"`;
const start = Date.now();
let stdout: Buffer;
let stderr: Buffer;

View File

@ -17,11 +17,22 @@ namespace ts.server {
loggingEnabled(): boolean;
perftrc(s: string): void;
info(s: string): void;
err(s: string): void;
group(logGroupEntries: (log: (msg: string) => void) => void): void;
startGroup(): void;
endGroup(): void;
msg(s: string, type?: Msg.Types): void;
getLogFileName(): string;
}
export namespace Msg {
export type Err = "Err";
export const Err: Err = "Err";
export type Info = "Info";
export const Info: Info = "Info";
export type Perf = "Perf";
export const Perf: Perf = "Perf";
export type Types = Err | Info | Perf;
}
function getProjectRootPath(project: Project): Path {
switch (project.projectKind) {
case ProjectKind.Configured:
@ -115,9 +126,7 @@ namespace ts.server {
}
export function createNormalizedPathMap<T>(): NormalizedPathMap<T> {
/* tslint:disable:no-null-keyword */
const map = createMap<T>();
/* tslint:enable:no-null-keyword */
return {
get(path) {
return map.get(path);

View File

@ -1150,6 +1150,56 @@ namespace ts.formatting {
}
}
/**
* @param precedingToken pass `null` if preceding token was already computed and result was `undefined`.
*/
export function getRangeOfEnclosingComment(
sourceFile: SourceFile,
position: number,
onlyMultiLine: boolean,
precedingToken?: Node | null, // tslint:disable-line:no-null-keyword
tokenAtPosition = getTokenAtPosition(sourceFile, position, /*includeJsDocComment*/ false),
predicate?: (c: CommentRange) => boolean): CommentRange | undefined {
const tokenStart = tokenAtPosition.getStart(sourceFile);
if (tokenStart <= position && position < tokenAtPosition.getEnd()) {
return undefined;
}
if (precedingToken === undefined) {
precedingToken = findPrecedingToken(position, sourceFile);
}
// Between two consecutive tokens, all comments are either trailing on the former
// or leading on the latter (and none are in both lists).
const trailingRangesOfPreviousToken = precedingToken && getTrailingCommentRanges(sourceFile.text, precedingToken.end);
const leadingCommentRangesOfNextToken = getLeadingCommentRangesOfNode(tokenAtPosition, sourceFile);
const commentRanges = trailingRangesOfPreviousToken && leadingCommentRangesOfNextToken ?
trailingRangesOfPreviousToken.concat(leadingCommentRangesOfNextToken) :
trailingRangesOfPreviousToken || leadingCommentRangesOfNextToken;
if (commentRanges) {
for (const range of commentRanges) {
// The end marker of a single-line comment does not include the newline character.
// With caret at `^`, in the following case, we are inside a comment (^ denotes the cursor position):
//
// // asdf ^\n
//
// But for closed multi-line comments, we don't want to be inside the comment in the following case:
//
// /* asdf */^
//
// However, unterminated multi-line comments *do* contain their end.
//
// Internally, we represent the end of the comment at the newline and closing '/', respectively.
//
if ((range.pos < position && position < range.end ||
position === range.end && (range.kind === SyntaxKind.SingleLineCommentTrivia || position === sourceFile.getFullWidth()))) {
return (range.kind === SyntaxKind.MultiLineCommentTrivia || !onlyMultiLine) && (!predicate || predicate(range)) ? range : undefined;
}
}
}
return undefined;
}
function getOpenTokenForList(node: Node, list: ReadonlyArray<Node>) {
switch (node.kind) {
case SyntaxKind.Constructor:

View File

@ -32,13 +32,36 @@ namespace ts.formatting {
}
const precedingToken = findPrecedingToken(position, sourceFile);
const enclosingCommentRange = getRangeOfEnclosingComment(sourceFile, position, /*onlyMultiLine*/ true, precedingToken || null); // tslint:disable-line:no-null-keyword
if (enclosingCommentRange) {
const previousLine = getLineAndCharacterOfPosition(sourceFile, position).line - 1;
const commentStartLine = getLineAndCharacterOfPosition(sourceFile, enclosingCommentRange.pos).line;
Debug.assert(commentStartLine >= 0);
if (previousLine <= commentStartLine) {
return findFirstNonWhitespaceColumn(getStartPositionOfLine(commentStartLine, sourceFile), position, sourceFile, options);
}
const startPostionOfLine = getStartPositionOfLine(previousLine, sourceFile);
const { column, character } = findFirstNonWhitespaceCharacterAndColumn(startPostionOfLine, position, sourceFile, options);
if (column === 0) {
return column;
}
const firstNonWhitespaceCharacterCode = sourceFile.text.charCodeAt(startPostionOfLine + character);
return firstNonWhitespaceCharacterCode === CharacterCodes.asterisk ? column - 1 : column;
}
if (!precedingToken) {
return getBaseIndentation(options);
}
// no indentation in string \regex\template literals
const precedingTokenIsLiteral = isStringOrRegularExpressionOrTemplateLiteral(precedingToken.kind);
if (precedingTokenIsLiteral && precedingToken.getStart(sourceFile) <= position && precedingToken.end > position) {
if (precedingTokenIsLiteral && precedingToken.getStart(sourceFile) <= position && position < precedingToken.end) {
return 0;
}
@ -405,13 +428,13 @@ namespace ts.formatting {
return findFirstNonWhitespaceColumn(lineStart, lineStart + lineAndCharacter.character, sourceFile, options);
}
/*
Character is the actual index of the character since the beginning of the line.
Column - position of the character after expanding tabs to spaces
"0\t2$"
value of 'character' for '$' is 3
value of 'column' for '$' is 6 (assuming that tab size is 4)
*/
/**
* Character is the actual index of the character since the beginning of the line.
* Column - position of the character after expanding tabs to spaces.
* "0\t2$"
* value of 'character' for '$' is 3
* value of 'column' for '$' is 6 (assuming that tab size is 4)
*/
export function findFirstNonWhitespaceCharacterAndColumn(startPos: number, endPos: number, sourceFile: SourceFileLike, options: EditorSettings) {
let character = 0;
let column = 0;

View File

@ -1,13 +1,20 @@
/* @internal */
namespace ts.OutliningElementsCollector {
const collapseText = "...";
const maxDepth = 20;
export function collectElements(sourceFile: SourceFile, cancellationToken: CancellationToken): OutliningSpan[] {
const elements: OutliningSpan[] = [];
const collapseText = "...";
let depth = 0;
function addOutliningSpan(hintSpanNode: Node, startElement: Node, endElement: Node, autoCollapse: boolean) {
walk(sourceFile);
return elements;
/** If useFullStart is true, then the collapsing span includes leading whitespace, including linebreaks. */
function addOutliningSpan(hintSpanNode: Node, startElement: Node, endElement: Node, autoCollapse: boolean, useFullStart: boolean) {
if (hintSpanNode && startElement && endElement) {
const span: OutliningSpan = {
textSpan: createTextSpanFromBounds(startElement.pos, endElement.end),
textSpan: createTextSpanFromBounds(useFullStart ? startElement.getFullStart() : startElement.getStart(), endElement.getEnd()),
hintSpan: createTextSpanFromNode(hintSpanNode, sourceFile),
bannerText: collapseText,
autoCollapse,
@ -82,8 +89,6 @@ namespace ts.OutliningElementsCollector {
return isFunctionBlock(node) && node.parent.kind !== SyntaxKind.ArrowFunction;
}
let depth = 0;
const maxDepth = 20;
function walk(n: Node): void {
cancellationToken.throwIfCancellationRequested();
if (depth > maxDepth) {
@ -113,7 +118,7 @@ namespace ts.OutliningElementsCollector {
parent.kind === SyntaxKind.WithStatement ||
parent.kind === SyntaxKind.CatchClause) {
addOutliningSpan(parent, openBrace, closeBrace, autoCollapse(n));
addOutliningSpan(parent, openBrace, closeBrace, autoCollapse(n), /*useFullStart*/ true);
break;
}
@ -121,13 +126,13 @@ namespace ts.OutliningElementsCollector {
// Could be the try-block, or the finally-block.
const tryStatement = <TryStatement>parent;
if (tryStatement.tryBlock === n) {
addOutliningSpan(parent, openBrace, closeBrace, autoCollapse(n));
addOutliningSpan(parent, openBrace, closeBrace, autoCollapse(n), /*useFullStart*/ true);
break;
}
else if (tryStatement.finallyBlock === n) {
const finallyKeyword = findChildOfKind(tryStatement, SyntaxKind.FinallyKeyword, sourceFile);
if (finallyKeyword) {
addOutliningSpan(finallyKeyword, openBrace, closeBrace, autoCollapse(n));
addOutliningSpan(finallyKeyword, openBrace, closeBrace, autoCollapse(n), /*useFullStart*/ true);
break;
}
}
@ -151,31 +156,35 @@ namespace ts.OutliningElementsCollector {
case SyntaxKind.ModuleBlock: {
const openBrace = findChildOfKind(n, SyntaxKind.OpenBraceToken, sourceFile);
const closeBrace = findChildOfKind(n, SyntaxKind.CloseBraceToken, sourceFile);
addOutliningSpan(n.parent, openBrace, closeBrace, autoCollapse(n));
addOutliningSpan(n.parent, openBrace, closeBrace, autoCollapse(n), /*useFullStart*/ true);
break;
}
case SyntaxKind.ClassDeclaration:
case SyntaxKind.InterfaceDeclaration:
case SyntaxKind.EnumDeclaration:
case SyntaxKind.ObjectLiteralExpression:
case SyntaxKind.CaseBlock: {
const openBrace = findChildOfKind(n, SyntaxKind.OpenBraceToken, sourceFile);
const closeBrace = findChildOfKind(n, SyntaxKind.CloseBraceToken, sourceFile);
addOutliningSpan(n, openBrace, closeBrace, autoCollapse(n));
addOutliningSpan(n, openBrace, closeBrace, autoCollapse(n), /*useFullStart*/ true);
break;
}
// If the block has no leading keywords and is inside an array literal,
// we only want to collapse the span of the block.
// Otherwise, the collapsed section will include the end of the previous line.
case SyntaxKind.ObjectLiteralExpression:
const openBrace = findChildOfKind(n, SyntaxKind.OpenBraceToken, sourceFile);
const closeBrace = findChildOfKind(n, SyntaxKind.CloseBraceToken, sourceFile);
addOutliningSpan(n, openBrace, closeBrace, autoCollapse(n), /*useFullStart*/ !isArrayLiteralExpression(n.parent));
break;
case SyntaxKind.ArrayLiteralExpression:
const openBracket = findChildOfKind(n, SyntaxKind.OpenBracketToken, sourceFile);
const closeBracket = findChildOfKind(n, SyntaxKind.CloseBracketToken, sourceFile);
addOutliningSpan(n, openBracket, closeBracket, autoCollapse(n));
addOutliningSpan(n, openBracket, closeBracket, autoCollapse(n), /*useFullStart*/ !isArrayLiteralExpression(n.parent));
break;
}
depth++;
forEachChild(n, walk);
depth--;
}
walk(sourceFile);
return elements;
}
}

View File

@ -856,7 +856,7 @@ namespace ts.refactor.extractMethod {
Write = 2
}
interface UsageEntry {
export interface UsageEntry {
readonly usage: Usage;
readonly symbol: Symbol;
readonly node: Node;

View File

@ -107,12 +107,14 @@ namespace ts {
scanner.setTextPos(pos);
while (pos < end) {
const token = scanner.scan();
Debug.assert(token !== SyntaxKind.EndOfFileToken); // Else it would infinitely loop
const textPos = scanner.getTextPos();
if (textPos <= end) {
nodes.push(createNode(token, pos, textPos, this));
}
pos = textPos;
if (token === SyntaxKind.EndOfFileToken) {
break;
}
}
return pos;
}
@ -1757,17 +1759,20 @@ namespace ts {
function getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: FormatCodeOptions | FormatCodeSettings): TextChange[] {
const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName);
const settings = toEditorSettings(options);
if (key === "{") {
return formatting.formatOnOpeningCurly(position, sourceFile, getRuleProvider(settings), settings);
}
else if (key === "}") {
return formatting.formatOnClosingCurly(position, sourceFile, getRuleProvider(settings), settings);
}
else if (key === ";") {
return formatting.formatOnSemicolon(position, sourceFile, getRuleProvider(settings), settings);
}
else if (key === "\n") {
return formatting.formatOnEnter(position, sourceFile, getRuleProvider(settings), settings);
if (!isInComment(sourceFile, position)) {
if (key === "{") {
return formatting.formatOnOpeningCurly(position, sourceFile, getRuleProvider(settings), settings);
}
else if (key === "}") {
return formatting.formatOnClosingCurly(position, sourceFile, getRuleProvider(settings), settings);
}
else if (key === ";") {
return formatting.formatOnSemicolon(position, sourceFile, getRuleProvider(settings), settings);
}
else if (key === "\n") {
return formatting.formatOnEnter(position, sourceFile, getRuleProvider(settings), settings);
}
}
return [];
@ -1826,6 +1831,12 @@ namespace ts {
return true;
}
function getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean) {
const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName);
const range = ts.formatting.getRangeOfEnclosingComment(sourceFile, position, onlyMultiLine);
return range && createTextSpanFromRange(range);
}
function getTodoComments(fileName: string, descriptors: TodoCommentDescriptor[]): TodoComment[] {
// Note: while getting todo comments seems like a syntactic operation, we actually
// treat it as a semantic operation here. This is because we expect our host to call
@ -2050,6 +2061,7 @@ namespace ts {
getFormattingEditsAfterKeystroke,
getDocCommentTemplateAtPosition,
isValidBraceCompletionAtPosition,
getSpanOfEnclosingComment,
getCodeFixesAtPosition,
getEmitOutput,
getNonBoundSourceFile,

View File

@ -254,6 +254,11 @@ namespace ts {
*/
isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): string;
/**
* Returns a JSON-encoded TextSpan | undefined indicating the range of the enclosing comment, if it exists.
*/
getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): string;
getEmitOutput(fileName: string): string;
getEmitOutputObject(fileName: string): EmitOutput;
}
@ -815,6 +820,13 @@ namespace ts {
);
}
public getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): string {
return this.forwardJSONCall(
`getSpanOfEnclosingComment('${fileName}', ${position})`,
() => this.languageService.getSpanOfEnclosingComment(fileName, position, onlyMultiLine)
);
}
/// GET SMART INDENT
public getIndentationAtPosition(fileName: string, position: number, options: string /*Services.EditorOptions*/): string {
return this.forwardJSONCall(

View File

@ -111,124 +111,123 @@ namespace ts.SymbolDisplay {
let signature: Signature;
type = isThisExpression ? typeChecker.getTypeAtLocation(location) : typeChecker.getTypeOfSymbolAtLocation(symbol.exportSymbol || symbol, location);
if (type) {
if (location.parent && location.parent.kind === SyntaxKind.PropertyAccessExpression) {
const right = (<PropertyAccessExpression>location.parent).name;
// Either the location is on the right of a property access, or on the left and the right is missing
if (right === location || (right && right.getFullWidth() === 0)) {
location = location.parent;
if (location.parent && location.parent.kind === SyntaxKind.PropertyAccessExpression) {
const right = (<PropertyAccessExpression>location.parent).name;
// Either the location is on the right of a property access, or on the left and the right is missing
if (right === location || (right && right.getFullWidth() === 0)) {
location = location.parent;
}
}
// try get the call/construct signature from the type if it matches
let callExpressionLike: CallExpression | NewExpression | JsxOpeningLikeElement;
if (isCallOrNewExpression(location)) {
callExpressionLike = <CallExpression | NewExpression>location;
}
else if (isCallExpressionTarget(location) || isNewExpressionTarget(location)) {
callExpressionLike = <CallExpression | NewExpression>location.parent;
}
else if (location.parent && isJsxOpeningLikeElement(location.parent) && isFunctionLike(symbol.valueDeclaration)) {
callExpressionLike = <JsxOpeningLikeElement>location.parent;
}
if (callExpressionLike) {
const candidateSignatures: Signature[] = [];
signature = typeChecker.getResolvedSignature(callExpressionLike, candidateSignatures);
if (!signature && candidateSignatures.length) {
// Use the first candidate:
signature = candidateSignatures[0];
}
const useConstructSignatures = callExpressionLike.kind === SyntaxKind.NewExpression || (isCallExpression(callExpressionLike) && callExpressionLike.expression.kind === SyntaxKind.SuperKeyword);
const allSignatures = useConstructSignatures ? type.getConstructSignatures() : type.getCallSignatures();
if (!contains(allSignatures, signature.target) && !contains(allSignatures, signature)) {
// Get the first signature if there is one -- allSignatures may contain
// either the original signature or its target, so check for either
signature = allSignatures.length ? allSignatures[0] : undefined;
}
if (signature) {
if (useConstructSignatures && (symbolFlags & SymbolFlags.Class)) {
// Constructor
symbolKind = ScriptElementKind.constructorImplementationElement;
addPrefixForAnyFunctionOrVar(type.symbol, symbolKind);
}
}
// try get the call/construct signature from the type if it matches
let callExpressionLike: CallExpression | NewExpression | JsxOpeningLikeElement;
if (isCallOrNewExpression(location)) {
callExpressionLike = <CallExpression | NewExpression>location;
}
else if (isCallExpressionTarget(location) || isNewExpressionTarget(location)) {
callExpressionLike = <CallExpression | NewExpression>location.parent;
}
else if (location.parent && isJsxOpeningLikeElement(location.parent) && isFunctionLike(symbol.valueDeclaration)) {
callExpressionLike = <JsxOpeningLikeElement>location.parent;
}
if (callExpressionLike) {
const candidateSignatures: Signature[] = [];
signature = typeChecker.getResolvedSignature(callExpressionLike, candidateSignatures);
if (!signature && candidateSignatures.length) {
// Use the first candidate:
signature = candidateSignatures[0];
}
const useConstructSignatures = callExpressionLike.kind === SyntaxKind.NewExpression || (isCallExpression(callExpressionLike) && callExpressionLike.expression.kind === SyntaxKind.SuperKeyword);
const allSignatures = useConstructSignatures ? type.getConstructSignatures() : type.getCallSignatures();
if (!contains(allSignatures, signature.target) && !contains(allSignatures, signature)) {
// Get the first signature if there is one -- allSignatures may contain
// either the original signature or its target, so check for either
signature = allSignatures.length ? allSignatures[0] : undefined;
}
if (signature) {
if (useConstructSignatures && (symbolFlags & SymbolFlags.Class)) {
// Constructor
symbolKind = ScriptElementKind.constructorImplementationElement;
addPrefixForAnyFunctionOrVar(type.symbol, symbolKind);
else if (symbolFlags & SymbolFlags.Alias) {
symbolKind = ScriptElementKind.alias;
pushTypePart(symbolKind);
displayParts.push(spacePart());
if (useConstructSignatures) {
displayParts.push(keywordPart(SyntaxKind.NewKeyword));
displayParts.push(spacePart());
}
else if (symbolFlags & SymbolFlags.Alias) {
symbolKind = ScriptElementKind.alias;
pushTypePart(symbolKind);
addFullSymbolName(symbol);
}
else {
addPrefixForAnyFunctionOrVar(symbol, symbolKind);
}
switch (symbolKind) {
case ScriptElementKind.jsxAttribute:
case ScriptElementKind.memberVariableElement:
case ScriptElementKind.variableElement:
case ScriptElementKind.constElement:
case ScriptElementKind.letElement:
case ScriptElementKind.parameterElement:
case ScriptElementKind.localVariableElement:
// If it is call or construct signature of lambda's write type name
displayParts.push(punctuationPart(SyntaxKind.ColonToken));
displayParts.push(spacePart());
if (useConstructSignatures) {
displayParts.push(keywordPart(SyntaxKind.NewKeyword));
displayParts.push(spacePart());
}
addFullSymbolName(symbol);
}
else {
addPrefixForAnyFunctionOrVar(symbol, symbolKind);
}
if (!(type.flags & TypeFlags.Object && (<ObjectType>type).objectFlags & ObjectFlags.Anonymous) && type.symbol) {
addRange(displayParts, symbolToDisplayParts(typeChecker, type.symbol, enclosingDeclaration, /*meaning*/ undefined, SymbolFormatFlags.WriteTypeParametersOrArguments));
}
addSignatureDisplayParts(signature, allSignatures, TypeFormatFlags.WriteArrowStyleSignature);
break;
switch (symbolKind) {
case ScriptElementKind.jsxAttribute:
case ScriptElementKind.memberVariableElement:
case ScriptElementKind.variableElement:
case ScriptElementKind.constElement:
case ScriptElementKind.letElement:
case ScriptElementKind.parameterElement:
case ScriptElementKind.localVariableElement:
// If it is call or construct signature of lambda's write type name
displayParts.push(punctuationPart(SyntaxKind.ColonToken));
displayParts.push(spacePart());
if (useConstructSignatures) {
displayParts.push(keywordPart(SyntaxKind.NewKeyword));
displayParts.push(spacePart());
}
if (!(type.flags & TypeFlags.Object && (<ObjectType>type).objectFlags & ObjectFlags.Anonymous) && type.symbol) {
addRange(displayParts, symbolToDisplayParts(typeChecker, type.symbol, enclosingDeclaration, /*meaning*/ undefined, SymbolFormatFlags.WriteTypeParametersOrArguments));
}
addSignatureDisplayParts(signature, allSignatures, TypeFormatFlags.WriteArrowStyleSignature);
break;
default:
// Just signature
addSignatureDisplayParts(signature, allSignatures);
}
hasAddedSymbolInfo = true;
default:
// Just signature
addSignatureDisplayParts(signature, allSignatures);
}
hasAddedSymbolInfo = true;
}
else if ((isNameOfFunctionDeclaration(location) && !(symbolFlags & SymbolFlags.Accessor)) || // name of function declaration
(location.kind === SyntaxKind.ConstructorKeyword && location.parent.kind === SyntaxKind.Constructor)) { // At constructor keyword of constructor declaration
// get the signature from the declaration and write it
const functionDeclaration = <FunctionLike>location.parent;
// Use function declaration to write the signatures only if the symbol corresponding to this declaration
const locationIsSymbolDeclaration = find(symbol.declarations, declaration =>
declaration === (location.kind === SyntaxKind.ConstructorKeyword ? functionDeclaration.parent : functionDeclaration));
}
else if ((isNameOfFunctionDeclaration(location) && !(symbolFlags & SymbolFlags.Accessor)) || // name of function declaration
(location.kind === SyntaxKind.ConstructorKeyword && location.parent.kind === SyntaxKind.Constructor)) { // At constructor keyword of constructor declaration
// get the signature from the declaration and write it
const functionDeclaration = <FunctionLike>location.parent;
// Use function declaration to write the signatures only if the symbol corresponding to this declaration
const locationIsSymbolDeclaration = find(symbol.declarations, declaration =>
declaration === (location.kind === SyntaxKind.ConstructorKeyword ? functionDeclaration.parent : functionDeclaration));
if (locationIsSymbolDeclaration) {
const allSignatures = functionDeclaration.kind === SyntaxKind.Constructor ? type.getNonNullableType().getConstructSignatures() : type.getNonNullableType().getCallSignatures();
if (!typeChecker.isImplementationOfOverload(functionDeclaration)) {
signature = typeChecker.getSignatureFromDeclaration(functionDeclaration);
}
else {
signature = allSignatures[0];
}
if (functionDeclaration.kind === SyntaxKind.Constructor) {
// show (constructor) Type(...) signature
symbolKind = ScriptElementKind.constructorImplementationElement;
addPrefixForAnyFunctionOrVar(type.symbol, symbolKind);
}
else {
// (function/method) symbol(..signature)
addPrefixForAnyFunctionOrVar(functionDeclaration.kind === SyntaxKind.CallSignature &&
!(type.symbol.flags & SymbolFlags.TypeLiteral || type.symbol.flags & SymbolFlags.ObjectLiteral) ? type.symbol : symbol, symbolKind);
}
addSignatureDisplayParts(signature, allSignatures);
hasAddedSymbolInfo = true;
if (locationIsSymbolDeclaration) {
const allSignatures = functionDeclaration.kind === SyntaxKind.Constructor ? type.getNonNullableType().getConstructSignatures() : type.getNonNullableType().getCallSignatures();
if (!typeChecker.isImplementationOfOverload(functionDeclaration)) {
signature = typeChecker.getSignatureFromDeclaration(functionDeclaration);
}
else {
signature = allSignatures[0];
}
if (functionDeclaration.kind === SyntaxKind.Constructor) {
// show (constructor) Type(...) signature
symbolKind = ScriptElementKind.constructorImplementationElement;
addPrefixForAnyFunctionOrVar(type.symbol, symbolKind);
}
else {
// (function/method) symbol(..signature)
addPrefixForAnyFunctionOrVar(functionDeclaration.kind === SyntaxKind.CallSignature &&
!(type.symbol.flags & SymbolFlags.TypeLiteral || type.symbol.flags & SymbolFlags.ObjectLiteral) ? type.symbol : symbol, symbolKind);
}
addSignatureDisplayParts(signature, allSignatures);
hasAddedSymbolInfo = true;
}
}
}
@ -417,7 +416,9 @@ namespace ts.SymbolDisplay {
symbolFlags & SymbolFlags.Accessor ||
symbolKind === ScriptElementKind.memberFunctionElement) {
const allSignatures = type.getNonNullableType().getCallSignatures();
addSignatureDisplayParts(allSignatures[0], allSignatures);
if (allSignatures.length) {
addSignatureDisplayParts(allSignatures[0], allSignatures);
}
}
}
}

View File

@ -270,6 +270,8 @@ namespace ts {
isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean;
getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): TextSpan;
getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[], formatOptions: FormatCodeSettings): CodeAction[];
getApplicableRefactors(fileName: string, positionOrRaneg: number | TextRange): ApplicableRefactorInfo[];
getEditsForRefactor(fileName: string, formatOptions: FormatCodeSettings, positionOrRange: number | TextRange, refactorName: string, actionName: string): RefactorEditInfo | undefined;

View File

@ -720,8 +720,14 @@ namespace ts {
}
}
export function findPrecedingToken(position: number, sourceFile: SourceFile, startNode?: Node, includeJsDoc?: boolean): Node {
return find(startNode || sourceFile);
/**
* Finds the rightmost token satisfying `token.end <= position`,
* excluding `JsxText` tokens containing only whitespace.
*/
export function findPrecedingToken(position: number, sourceFile: SourceFile, startNode?: Node, includeJsDoc?: boolean): Node | undefined {
const result = find(startNode || sourceFile);
Debug.assert(!(result && isWhiteSpaceOnlyJsxText(result)));
return result;
function findRightmostToken(n: Node): Node {
if (isToken(n)) {
@ -742,19 +748,17 @@ namespace ts {
const children = n.getChildren();
for (let i = 0; i < children.length; i++) {
const child = children[i];
// condition 'position < child.end' checks if child node end after the position
// in the example below this condition will be false for 'aaaa' and 'bbbb' and true for 'ccc'
// aaaa___bbbb___$__ccc
// after we found child node with end after the position we check if start of the node is after the position.
// if yes - then position is in the trivia and we need to look into the previous child to find the token in question.
// if no - position is in the node itself so we should recurse in it.
// NOTE: JsxText is a weird kind of node that can contain only whitespaces (since they are not counted as trivia).
// if this is the case - then we should assume that token in question is located in previous child.
if (position < child.end && (nodeHasTokens(child) || child.kind === SyntaxKind.JsxText)) {
// Note that the span of a node's tokens is [node.getStart(...), node.end).
// Given that `position < child.end` and child has constituent tokens, we distinguish these cases:
// 1) `position` precedes `child`'s tokens or `child` has no tokens (ie: in a comment or whitespace preceding `child`):
// we need to find the last token in a previous child.
// 2) `position` is within the same span: we recurse on `child`.
if (position < child.end) {
const start = child.getStart(sourceFile, includeJsDoc);
const lookInPreviousChild =
(start >= position) || // cursor in the leading trivia
(child.kind === SyntaxKind.JsxText && start === child.end); // whitespace only JsxText
!nodeHasTokens(child) ||
isWhiteSpaceOnlyJsxText(child);
if (lookInPreviousChild) {
// actual start of the node is past the position - previous token should be at the end of previous child
@ -780,10 +784,17 @@ namespace ts {
}
}
/// finds last node that is considered as candidate for search (isCandidate(node) === true) starting from 'exclusiveStartPosition'
/**
* Finds the rightmost child to the left of `children[exclusiveStartPosition]` which is a non-all-whitespace token or has constituent tokens.
*/
function findRightmostChildNodeWithTokens(children: Node[], exclusiveStartPosition: number): Node {
for (let i = exclusiveStartPosition - 1; i >= 0; i--) {
if (nodeHasTokens(children[i])) {
const child = children[i];
if (isWhiteSpaceOnlyJsxText(child)) {
Debug.assert(i > 0, "`JsxText` tokens should not be the first child of `JsxElement | JsxSelfClosingElement`");
}
else if (nodeHasTokens(children[i])) {
return children[i];
}
}
@ -851,6 +862,10 @@ namespace ts {
return false;
}
export function isWhiteSpaceOnlyJsxText(node: Node): node is JsxText {
return isJsxText(node) && node.containsOnlyWhiteSpaces;
}
export function isInTemplateString(sourceFile: SourceFile, position: number) {
const token = getTokenAtPosition(sourceFile, position, /*includeJsDocComment*/ false);
return isTemplateLiteralKind(token.kind) && position > token.getStart(sourceFile);
@ -863,41 +878,11 @@ namespace ts {
* @param predicate Additional predicate to test on the comment range.
*/
export function isInComment(
sourceFile: SourceFile,
position: number,
tokenAtPosition = getTokenAtPosition(sourceFile, position, /*includeJsDocComment*/ false),
predicate?: (c: CommentRange) => boolean): boolean {
return position <= tokenAtPosition.getStart(sourceFile) &&
(isInCommentRange(getLeadingCommentRanges(sourceFile.text, tokenAtPosition.pos)) ||
isInCommentRange(getTrailingCommentRanges(sourceFile.text, tokenAtPosition.pos)));
function isInCommentRange(commentRanges: CommentRange[]): boolean {
return forEach(commentRanges, c => isPositionInCommentRange(c, position, sourceFile.text) && (!predicate || predicate(c)));
}
}
function isPositionInCommentRange({ pos, end, kind }: ts.CommentRange, position: number, text: string): boolean {
if (pos < position && position < end) {
return true;
}
else if (position === end) {
// The end marker of a single-line comment does not include the newline character.
// In the following case, we are inside a comment (^ denotes the cursor position):
//
// // asdf ^\n
//
// But for multi-line comments, we don't want to be inside the comment in the following case:
//
// /* asdf */^
//
// Internally, we represent the end of the comment at the newline and closing '/', respectively.
return kind === SyntaxKind.SingleLineCommentTrivia ||
// true for unterminated multi-line comment
!(text.charCodeAt(end - 1) === CharacterCodes.slash && text.charCodeAt(end - 2) === CharacterCodes.asterisk);
}
else {
return false;
}
sourceFile: SourceFile,
position: number,
tokenAtPosition?: Node,
predicate?: (c: CommentRange) => boolean): boolean {
return !!formatting.getRangeOfEnclosingComment(sourceFile, position, /*onlyMultiLine*/ false, /*precedingToken*/ undefined, tokenAtPosition, predicate);
}
export function hasDocComment(sourceFile: SourceFile, position: number) {
@ -916,7 +901,7 @@ namespace ts {
function nodeHasTokens(n: Node): boolean {
// If we have a token or node that has a non-zero width, it must have tokens.
// Note, that getWidth() does not take trivia into account.
// Note: getWidth() does not take trivia into account.
return n.getWidth() !== 0;
}

View File

@ -0,0 +1,19 @@
tests/cases/compiler/baseExpressionTypeParameters.ts(10,27): error TS2561: Base class expressions cannot reference class type parameters.
==== tests/cases/compiler/baseExpressionTypeParameters.ts (1 errors) ====
// Repro from #17829
function base<T>() {
class Base {
static prop: T;
}
return Base;
}
class Gen<T> extends base<T>() {} // Error, T not in scope
~
!!! error TS2561: Base class expressions cannot reference class type parameters.
class Spec extends Gen<string> {}
<string>Spec.prop;

View File

@ -0,0 +1,50 @@
//// [baseExpressionTypeParameters.ts]
// Repro from #17829
function base<T>() {
class Base {
static prop: T;
}
return Base;
}
class Gen<T> extends base<T>() {} // Error, T not in scope
class Spec extends Gen<string> {}
<string>Spec.prop;
//// [baseExpressionTypeParameters.js]
// Repro from #17829
var __extends = (this && this.__extends) || (function () {
var extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
function base() {
var Base = /** @class */ (function () {
function Base() {
}
return Base;
}());
return Base;
}
var Gen = /** @class */ (function (_super) {
__extends(Gen, _super);
function Gen() {
return _super !== null && _super.apply(this, arguments) || this;
}
return Gen;
}(base())); // Error, T not in scope
var Spec = /** @class */ (function (_super) {
__extends(Spec, _super);
function Spec() {
return _super !== null && _super.apply(this, arguments) || this;
}
return Spec;
}(Gen));
Spec.prop;

View File

@ -0,0 +1,42 @@
//// [cloduleGenericOnSelfMember.ts]
class ServiceBase<T> {
field: T;
}
class Service extends ServiceBase<typeof Service.Base> {
}
namespace Service {
export const Base = {
name: "1",
value: 5
};
}
//// [cloduleGenericOnSelfMember.js]
var __extends = (this && this.__extends) || (function () {
var extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var ServiceBase = /** @class */ (function () {
function ServiceBase() {
}
return ServiceBase;
}());
var Service = /** @class */ (function (_super) {
__extends(Service, _super);
function Service() {
return _super !== null && _super.apply(this, arguments) || this;
}
return Service;
}(ServiceBase));
(function (Service) {
Service.Base = {
name: "1",
value: 5
};
})(Service || (Service = {}));

View File

@ -0,0 +1,30 @@
=== tests/cases/compiler/cloduleGenericOnSelfMember.ts ===
class ServiceBase<T> {
>ServiceBase : Symbol(ServiceBase, Decl(cloduleGenericOnSelfMember.ts, 0, 0))
>T : Symbol(T, Decl(cloduleGenericOnSelfMember.ts, 0, 18))
field: T;
>field : Symbol(ServiceBase.field, Decl(cloduleGenericOnSelfMember.ts, 0, 22))
>T : Symbol(T, Decl(cloduleGenericOnSelfMember.ts, 0, 18))
}
class Service extends ServiceBase<typeof Service.Base> {
>Service : Symbol(Service, Decl(cloduleGenericOnSelfMember.ts, 2, 1), Decl(cloduleGenericOnSelfMember.ts, 4, 1))
>ServiceBase : Symbol(ServiceBase, Decl(cloduleGenericOnSelfMember.ts, 0, 0))
>Service.Base : Symbol(Service.Base, Decl(cloduleGenericOnSelfMember.ts, 6, 16))
>Service : Symbol(Service, Decl(cloduleGenericOnSelfMember.ts, 2, 1), Decl(cloduleGenericOnSelfMember.ts, 4, 1))
>Base : Symbol(Service.Base, Decl(cloduleGenericOnSelfMember.ts, 6, 16))
}
namespace Service {
>Service : Symbol(Service, Decl(cloduleGenericOnSelfMember.ts, 2, 1), Decl(cloduleGenericOnSelfMember.ts, 4, 1))
export const Base = {
>Base : Symbol(Base, Decl(cloduleGenericOnSelfMember.ts, 6, 16))
name: "1",
>name : Symbol(name, Decl(cloduleGenericOnSelfMember.ts, 6, 25))
value: 5
>value : Symbol(value, Decl(cloduleGenericOnSelfMember.ts, 7, 18))
};
}

View File

@ -0,0 +1,33 @@
=== tests/cases/compiler/cloduleGenericOnSelfMember.ts ===
class ServiceBase<T> {
>ServiceBase : ServiceBase<T>
>T : T
field: T;
>field : T
>T : T
}
class Service extends ServiceBase<typeof Service.Base> {
>Service : Service
>ServiceBase : ServiceBase<{ name: string; value: number; }>
>Service.Base : { name: string; value: number; }
>Service : typeof Service
>Base : { name: string; value: number; }
}
namespace Service {
>Service : typeof Service
export const Base = {
>Base : { name: string; value: number; }
>{ name: "1", value: 5 } : { name: string; value: number; }
name: "1",
>name : string
>"1" : "1"
value: 5
>value : number
>5 : 5
};
}

View File

@ -1,11 +1,11 @@
tests/cases/compiler/inheritFromGenericTypeParameter.ts(1,20): error TS2693: 'T' only refers to a type, but is being used as a value here.
tests/cases/compiler/inheritFromGenericTypeParameter.ts(1,20): error TS2304: Cannot find name 'T'.
tests/cases/compiler/inheritFromGenericTypeParameter.ts(2,24): error TS2312: An interface may only extend a class or another interface.
==== tests/cases/compiler/inheritFromGenericTypeParameter.ts (2 errors) ====
class C<T> extends T { }
~
!!! error TS2693: 'T' only refers to a type, but is being used as a value here.
!!! error TS2304: Cannot find name 'T'.
interface I<T> extends T { }
~
!!! error TS2312: An interface may only extend a class or another interface.

View File

@ -1,11 +1,11 @@
tests/cases/compiler/typeParameterAsBaseClass.ts(1,20): error TS2693: 'T' only refers to a type, but is being used as a value here.
tests/cases/compiler/typeParameterAsBaseClass.ts(1,20): error TS2304: Cannot find name 'T'.
tests/cases/compiler/typeParameterAsBaseClass.ts(2,24): error TS2422: A class may only implement another class or interface.
==== tests/cases/compiler/typeParameterAsBaseClass.ts (2 errors) ====
class C<T> extends T {}
~
!!! error TS2693: 'T' only refers to a type, but is being used as a value here.
!!! error TS2304: Cannot find name 'T'.
class C2<T> implements T {}
~
!!! error TS2422: A class may only implement another class or interface.

View File

@ -1,5 +1,5 @@
tests/cases/conformance/types/typeParameters/typeParameterAsBaseType.ts(4,20): error TS2693: 'T' only refers to a type, but is being used as a value here.
tests/cases/conformance/types/typeParameters/typeParameterAsBaseType.ts(5,24): error TS2693: 'U' only refers to a type, but is being used as a value here.
tests/cases/conformance/types/typeParameters/typeParameterAsBaseType.ts(4,20): error TS2304: Cannot find name 'T'.
tests/cases/conformance/types/typeParameters/typeParameterAsBaseType.ts(5,24): error TS2304: Cannot find name 'U'.
tests/cases/conformance/types/typeParameters/typeParameterAsBaseType.ts(7,24): error TS2312: An interface may only extend a class or another interface.
tests/cases/conformance/types/typeParameters/typeParameterAsBaseType.ts(8,28): error TS2312: An interface may only extend a class or another interface.
@ -10,10 +10,10 @@ tests/cases/conformance/types/typeParameters/typeParameterAsBaseType.ts(8,28): e
class C<T> extends T { }
~
!!! error TS2693: 'T' only refers to a type, but is being used as a value here.
!!! error TS2304: Cannot find name 'T'.
class C2<T, U> extends U { }
~
!!! error TS2693: 'U' only refers to a type, but is being used as a value here.
!!! error TS2304: Cannot find name 'U'.
interface I<T> extends T { }
~

View File

@ -0,0 +1,13 @@
// Repro from #17829
function base<T>() {
class Base {
static prop: T;
}
return Base;
}
class Gen<T> extends base<T>() {} // Error, T not in scope
class Spec extends Gen<string> {}
<string>Spec.prop;

View File

@ -0,0 +1,11 @@
class ServiceBase<T> {
field: T;
}
class Service extends ServiceBase<typeof Service.Base> {
}
namespace Service {
export const Base = {
name: "1",
value: 5
};
}

View File

@ -14,5 +14,4 @@
//////c./**/
goTo.marker();
verify.completionListIsEmpty();
verify.completionListIsEmpty();

View File

@ -0,0 +1,14 @@
/// <reference path="fourslash.ts"/>
//// /**
//// * /*1*/
//// */
goTo.marker("1");
edit.insertLine("");
verify.currentFileContentIs(
` /**
*
*/`
);

View File

@ -153,6 +153,7 @@ declare namespace FourSlashInterface {
typeDefinitionCountIs(expectedCount: number): void;
implementationListIsEmpty(): void;
isValidBraceCompletionAtPosition(openingBrace?: string): void;
isInCommentAtPosition(onlyMultiLineDiverges?: boolean): void;
codeFixAvailable(): void;
applicableRefactorAvailableAtMarker(markerName: string): void;
codeFixDiagnosticsAvailableAtMarkers(markerNames: string[], diagnosticCode?: number): void;

View File

@ -0,0 +1,56 @@
/// <reference path="fourslash.ts"/>
// objects in x should generate outlining spans that do not render in VS
//// const x =[| [
//// [|{ a: 0 }|],
//// [|{ b: 1 }|],
//// [|{ c: 2 }|]
//// ]|];
////
// objects in y should generate outlining spans that render as expected
//// const y =[| [
//// [|{
//// a: 0
//// }|],
//// [|{
//// b: 1
//// }|],
//// [|{
//// c: 2
//// }|]
//// ]|];
////
// same behavior for nested arrays
//// const w =[| [
//// [|[ 0 ]|],
//// [|[ 1 ]|],
//// [|[ 2 ]|]
//// ]|];
////
//// const z =[| [
//// [|[
//// 0
//// ]|],
//// [|[
//// 1
//// ]|],
//// [|[
//// 2
//// ]|]
//// ]|];
////
// multiple levels of nesting work as expected
//// const z =[| [
//// [|[
//// [|{ hello: 0 }|]
//// ]|],
//// [|[
//// [|{ hello: 3 }|]
//// ]|],
//// [|[
//// [|{ hello: 5 }|],
//// [|{ hello: 7 }|]
//// ]|]
//// ]|];
verify.outliningSpansInCurrentFile(test.ranges());

View File

@ -27,13 +27,13 @@ verify.indentationIs(4);
goTo.marker("2");
verify.indentationIs(4);
goTo.marker("3");
verify.indentationIs(4);
verify.indentationIs(3);
goTo.marker("4");
verify.indentationIs(4);
verify.indentationIs(3);
// Putting a marker in line "*" would bring some error when parsing code in automation.
// So move right by 1 offset from marker 4 to locate the caret in this line.
edit.moveRight(1);
verify.indentationIs(4);
verify.indentationIs(3);
// Putting a marker in line " */" would bring some error when parsing code in automation.
// So move left by 1 offset from marker 5 to locate the caret in this line.
goTo.marker("5");

View File

@ -0,0 +1,32 @@
/// <reference path='fourslash.ts' />
//// // /*0_0*/
//// /* /*0_1*/
//// some text /*0_2*/
//// some text /*1_0*/
//// * some text /*0_3*/
//// /*0_4*/
//// */
//// function foo() {
//// // /*4_0*/
//// /** /*4_1*/
//// * /*4_2*/
//// * /*4_3*/
//// /*7_0*/
//// */
//// /* /*4_4*/ */
//// }
for (let i = 0; i < 5; ++i) {
goTo.marker(`0_${i}`);
verify.indentationIs(0);
goTo.marker(`4_${i}`);
verify.indentationIs(4);
}
goTo.marker(`1_0`);
verify.indentationIs(1);
goTo.marker(`7_0`);
verify.indentationIs(7);

View File

@ -0,0 +1,46 @@
/// <reference path="fourslash.ts" />
//// /* x */
//// /**
//// * @param this doesn't make sense here.
//// */
//// // x
//// let x = 1; /*
//// *
const firstCommentStart = 0;
const firstCommentEnd = 7;
goTo.position(firstCommentStart);
verify.not.isInCommentAtPosition();
goTo.position(firstCommentStart + 1);
verify.isInCommentAtPosition();
goTo.position(firstCommentEnd - 1);
verify.isInCommentAtPosition();
goTo.position(firstCommentEnd);
verify.not.isInCommentAtPosition();
const multilineJsDocStart = firstCommentEnd + 1;
const multilineJsDocEnd = multilineJsDocStart + 49;
goTo.position(multilineJsDocStart);
verify.not.isInCommentAtPosition();
goTo.position(multilineJsDocStart + 1);
verify.isInCommentAtPosition();
goTo.position(multilineJsDocEnd - 1);
verify.isInCommentAtPosition();
goTo.position(multilineJsDocEnd);
verify.not.isInCommentAtPosition();
const singleLineCommentStart = multilineJsDocEnd + 1;
goTo.position(singleLineCommentStart + 1);
verify.isInCommentAtPosition(/*onlyMultiLineDiverges*/ true);
const postNodeCommentStart = singleLineCommentStart + 16;
goTo.position(postNodeCommentStart);
verify.not.isInCommentAtPosition();
goTo.position(postNodeCommentStart + 1);
verify.isInCommentAtPosition();

View File

@ -0,0 +1,27 @@
/// <reference path="fourslash.ts" />
// @Filename: file.jsx
//// <div>
//// // /*0*/
//// /* /*1*/ */
//// /**
//// * /*2*/
//// */
//// foo() /* /*3*/ */
//// // /*4*/
//// /* /*5*/ */
//// /**
//// * /*6*/
//// */
//// </div>
//// <div>
//// // /*7*/
//// /* /*8*/ */
//// /**
//// * /*9*/
//// */
for (let i = 0; i < 10; ++i) {
goTo.marker(i.toString());
verify.not.isInCommentAtPosition();
}

View File

@ -0,0 +1,27 @@
/// <reference path="fourslash.ts" />
// @Filename: file.jsx
//// `
//// // /*0*/
//// /* /*1*/ */
//// /**
//// * /*2*/
//// */
//// foo()
//// // /*3*/
//// /* /*4*/ */
//// /**
//// * /*5*/
//// */
//// `
//// `
//// // /*6*/
//// /* /*7*/ */
//// /**
//// * /*8*/
//// */
for (let i = 0; i < 9; ++i) {
goTo.marker(i.toString());
verify.not.isInCommentAtPosition();
}

View File

@ -0,0 +1,37 @@
/// <reference path="fourslash.ts" />
//// /* x */
//// /**
//// * @param this doesn't make sense here.
//// */
//// // x
const firstCommentStart = 0;
const firstCommentEnd = 7;
goTo.position(firstCommentStart);
verify.not.isInCommentAtPosition();
goTo.position(firstCommentStart + 1);
verify.isInCommentAtPosition();
goTo.position(firstCommentEnd - 1);
verify.isInCommentAtPosition();
goTo.position(firstCommentEnd);
verify.not.isInCommentAtPosition();
const multilineJsDocStart = firstCommentEnd + 1;
const multilineJsDocEnd = multilineJsDocStart + 49;
goTo.position(multilineJsDocStart);
verify.not.isInCommentAtPosition();
goTo.position(multilineJsDocStart + 1);
verify.isInCommentAtPosition();
goTo.position(multilineJsDocEnd - 1);
verify.isInCommentAtPosition();
goTo.position(multilineJsDocEnd);
verify.not.isInCommentAtPosition();
const singleLineCommentStart = multilineJsDocEnd + 1;
goTo.position(singleLineCommentStart + 1);
verify.isInCommentAtPosition(/*onlyMultiLineDiverges*/ true);

View File

@ -0,0 +1,10 @@
/// <reference path='fourslash.ts' />
////foo({
//// /**/f: function() {},
//// f() {}
////});
// The symbol indicates that this is a funciton, but the type is `any`.
// Regression test that we don't crash (by trying to get signatures from `any`).
verify.quickInfoAt("", "(method) f");