Skip to content

Commit

Permalink
feat: types as comments wip
Browse files Browse the repository at this point in the history
  • Loading branch information
somebody1234 committed Apr 14, 2022
1 parent 8cd4b7a commit 49be504
Show file tree
Hide file tree
Showing 8 changed files with 874 additions and 172 deletions.
12 changes: 12 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -6230,6 +6230,18 @@
"category": "Error",
"code": 8034
},
"Types can only be used in TypeScript files.": {
"category": "Error",
"code": 8035
},
"Comments-as-types cannot contain JavaScript.": {
"category": "Error",
"code": 8036
},
"The 'declare' modifier cannot be used on a JavaScript declaration.": {
"category": "Error",
"code": 8037
},
"Declaration emit for this file requires using private name '{0}'. An explicit type annotation may unblock declaration emit.": {
"category": "Error",
"code": 9005
Expand Down
767 changes: 620 additions & 147 deletions src/compiler/parser.ts

Large diffs are not rendered by default.

176 changes: 158 additions & 18 deletions src/compiler/program.ts

Large diffs are not rendered by default.

55 changes: 53 additions & 2 deletions src/compiler/scanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ namespace ts {
setTextPos(textPos: number): void;
/* @internal */
setInJSDocType(inType: boolean): void;
/* @internal */
setInTypeComment(inTypeComment: boolean): void;
// Invokes the provided callback then unconditionally restores the scanner to the state it
// was in immediately prior to invoking the callback. The result of invoking the callback
// is returned from this function.
Expand Down Expand Up @@ -226,6 +228,9 @@ namespace ts {
"@": SyntaxKind.AtToken,
"#": SyntaxKind.HashToken,
"`": SyntaxKind.BacktickToken,
"/*::": SyntaxKind.SlashAsteriskColonColonToken,
"/*:": SyntaxKind.SlashAsteriskColonToken,
"*/": SyntaxKind.AsteriskSlashToken,
}));

/*
Expand Down Expand Up @@ -559,7 +564,7 @@ namespace ts {
}

/* @internal */
export function skipTrivia(text: string, pos: number, stopAfterLineBreak?: boolean, stopAtComments?: boolean, inJSDoc?: boolean): number {
export function skipTrivia(text: string, pos: number, stopAfterLineBreak?: boolean, stopAtComments?: boolean, inJSDoc?: boolean, inTypeComment?: boolean): number {
if (positionIsSynthesized(pos)) {
return pos;
}
Expand Down Expand Up @@ -597,12 +602,18 @@ namespace ts {
if (isLineBreak(text.charCodeAt(pos))) {
break;
}
if (inTypeComment && text.charCodeAt(pos) === CharacterCodes.asterisk && text.charCodeAt(pos + 1) === CharacterCodes.slash) {
return pos;
}
pos++;
}
canConsumeStar = false;
continue;
}
if (text.charCodeAt(pos + 1) === CharacterCodes.asterisk) {
if (text.charCodeAt(pos + 2) === CharacterCodes.colon) {
break;
}
pos += 2;
while (pos < text.length) {
if (text.charCodeAt(pos) === CharacterCodes.asterisk && text.charCodeAt(pos + 1) === CharacterCodes.slash) {
Expand Down Expand Up @@ -793,8 +804,8 @@ namespace ts {
if (nextChar === CharacterCodes.slash || nextChar === CharacterCodes.asterisk) {
const kind = nextChar === CharacterCodes.slash ? SyntaxKind.SingleLineCommentTrivia : SyntaxKind.MultiLineCommentTrivia;
const startPos = pos;
pos += 2;
if (nextChar === CharacterCodes.slash) {
pos += 2;
while (pos < text.length) {
if (isLineBreak(text.charCodeAt(pos))) {
hasTrailingNewLine = true;
Expand All @@ -804,6 +815,11 @@ namespace ts {
}
}
else {
if (text.charCodeAt(pos + 2) === CharacterCodes.colon) {
break scan;
}

pos += 2;
while (pos < text.length) {
if (text.charCodeAt(pos) === CharacterCodes.asterisk && text.charCodeAt(pos + 1) === CharacterCodes.slash) {
pos += 2;
Expand Down Expand Up @@ -956,6 +972,7 @@ namespace ts {

let commentDirectives: CommentDirective[] | undefined;
let inJSDocType = 0;
let inTypeComment = false;

setText(text, start, length);

Expand Down Expand Up @@ -1000,6 +1017,7 @@ namespace ts {
setOnError,
setTextPos,
setInJSDocType,
setInTypeComment,
tryScan,
lookAhead,
scanRange,
Expand Down Expand Up @@ -1611,6 +1629,7 @@ namespace ts {
startPos = pos;
tokenFlags = TokenFlags.None;
let asteriskSeen = false;

while (true) {
tokenPos = pos;
if (pos >= end) {
Expand Down Expand Up @@ -1728,6 +1747,12 @@ namespace ts {
}
return pos += 2, token = SyntaxKind.AsteriskAsteriskToken;
}
if (text.charCodeAt(pos + 1) === CharacterCodes.slash) {
inTypeComment = false;
// Could technically be `0*/regex/` but we assume that never happens
// Otherwise, we could just only do this if inTypeComment is `true`
return pos += 2, token = SyntaxKind.AsteriskSlashToken;
}
pos++;
if (inJSDocType && !asteriskSeen && (tokenFlags & TokenFlags.PrecedingLineBreak)) {
// decoration at the start of a JSDoc comment line
Expand Down Expand Up @@ -1775,6 +1800,10 @@ namespace ts {
if (isLineBreak(text.charCodeAt(pos))) {
break;
}
if (inTypeComment && text.charCodeAt(pos) === CharacterCodes.asterisk && text.charCodeAt(pos + 1) === CharacterCodes.slash) {
inTypeComment = false;
return pos += 2, token = SyntaxKind.AsteriskSlashToken;
}
pos++;
}

Expand All @@ -1794,6 +1823,19 @@ namespace ts {
}
// Multi-line comment
if (text.charCodeAt(pos + 1) === CharacterCodes.asterisk) {
if (inTypeComment) {
pos++;
return token = SyntaxKind.SlashToken;
}

if (text.charCodeAt(pos + 2) === CharacterCodes.colon) {
inTypeComment = true;
if (text.charCodeAt(pos + 3) === CharacterCodes.colon) {
return pos += 4, token = SyntaxKind.SlashAsteriskColonColonToken;
}
return pos += 3, token = SyntaxKind.SlashAsteriskColonToken;
}

pos += 2;
if (text.charCodeAt(pos) === CharacterCodes.asterisk && text.charCodeAt(pos + 1) !== CharacterCodes.slash) {
tokenFlags |= TokenFlags.PrecedingJSDocComment;
Expand Down Expand Up @@ -2493,6 +2535,7 @@ namespace ts {
const saveToken = token;
const saveTokenValue = tokenValue;
const saveTokenFlags = tokenFlags;
const saveInTypeComment = inTypeComment;
const result = callback();

// If our callback returned something 'falsy' or we're just looking ahead,
Expand All @@ -2504,6 +2547,7 @@ namespace ts {
token = saveToken;
tokenValue = saveTokenValue;
tokenFlags = saveTokenFlags;
inTypeComment = saveInTypeComment;
}
return result;
}
Expand All @@ -2516,6 +2560,7 @@ namespace ts {
const saveToken = token;
const saveTokenValue = tokenValue;
const saveTokenFlags = tokenFlags;
const saveInTypeComment = inTypeComment;
const saveErrorExpectations = commentDirectives;

setText(text, start, length);
Expand All @@ -2528,6 +2573,7 @@ namespace ts {
token = saveToken;
tokenValue = saveTokenValue;
tokenFlags = saveTokenFlags;
inTypeComment = saveInTypeComment;
commentDirectives = saveErrorExpectations;

return result;
Expand Down Expand Up @@ -2575,11 +2621,16 @@ namespace ts {
token = SyntaxKind.Unknown;
tokenValue = undefined!;
tokenFlags = TokenFlags.None;
inTypeComment = false;
}

function setInJSDocType(inType: boolean) {
inJSDocType += inType ? 1 : -1;
}

function setInTypeComment(inTypeComment_: boolean) {
inTypeComment = inTypeComment_;
}
}

/* @internal */
Expand Down
10 changes: 9 additions & 1 deletion src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,12 @@ namespace ts {
BacktickToken,
/** Only the JSDoc scanner produces HashToken. The normal scanner produces PrivateIdentifier. */
HashToken,
/** Marks the start of a comment type annotation. */
SlashAsteriskColonToken,
/** Marks the start of a comment type include. */
SlashAsteriskColonColonToken,
/** Marks the end of a comment type annotation. */
AsteriskSlashToken,
// Assignments
EqualsToken,
PlusEqualsToken,
Expand Down Expand Up @@ -787,6 +793,8 @@ namespace ts {
/* @internal */ TypeCached = 1 << 26, // If a type was cached for node at any point
/* @internal */ Deprecated = 1 << 27, // If has '@deprecated' JSDoc tag

/* @internal */ InTypeComment = 1 << 28, // If node was parsed inside comment-as-type

BlockScoped = Let | Const,

ReachabilityCheckFlags = HasImplicitReturn | HasExplicitReturn,
Expand Down Expand Up @@ -3187,7 +3195,7 @@ namespace ts {
enabled: boolean;
}

export type CommentKind = SyntaxKind.SingleLineCommentTrivia | SyntaxKind.MultiLineCommentTrivia;
export type CommentKind = | SyntaxKind.SingleLineCommentTrivia | SyntaxKind.MultiLineCommentTrivia;

export interface CommentRange extends TextRange {
hasTrailingNewLine?: boolean;
Expand Down
7 changes: 6 additions & 1 deletion src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1179,14 +1179,16 @@ namespace ts {
const isMissing = nodeIsMissing(errorNode);
const pos = isMissing || isJsxText(node)
? errorNode.pos
: skipTrivia(sourceFile.text, errorNode.pos);
: skipTrivia(sourceFile.text, errorNode.pos, false, false, false, !!(node.flags & NodeFlags.InTypeComment));

// These asserts should all be satisfied for a properly constructed `errorNode`.
if (isMissing) {
console.log('isMissing ' + isMissing + ' pos ' + pos + ' errKind ' + errorNode.kind + ' errPos ' + errorNode.pos + ' errEnd ' + errorNode.end + ' skipped "' + sourceFile.text.slice(errorNode.pos, pos) + '"');
Debug.assert(pos === errorNode.pos, "This failure could trigger https://github.com/Microsoft/TypeScript/issues/20809");
Debug.assert(pos === errorNode.end, "This failure could trigger https://github.com/Microsoft/TypeScript/issues/20809");
}
else {
console.log('isMissing ' + isMissing + ' pos ' + pos + ' errKind ' + errorNode.kind + ' errPos ' + errorNode.pos + ' errEnd ' + errorNode.end + ' skipped "' + sourceFile.text.slice(errorNode.pos, pos) + '"');
Debug.assert(pos >= errorNode.pos, "This failure could trigger https://github.com/Microsoft/TypeScript/issues/20809");
Debug.assert(pos <= errorNode.end, "This failure could trigger https://github.com/Microsoft/TypeScript/issues/20809");
}
Expand Down Expand Up @@ -3847,6 +3849,9 @@ namespace ts {
case SyntaxKind.InstanceOfKeyword:
case SyntaxKind.InKeyword:
case SyntaxKind.AsKeyword:
// Not an operator, but may be before "as"
// falls through
case SyntaxKind.SlashAsteriskColonColonToken:
return OperatorPrecedence.Relational;
case SyntaxKind.LessThanLessThanToken:
case SyntaxKind.GreaterThanGreaterThanToken:
Expand Down
2 changes: 1 addition & 1 deletion src/services/classifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -672,7 +672,7 @@ namespace ts {
break;

case SyntaxKind.ShebangTrivia:
// TODO: Maybe we should classify these.
// TODO: Maybe we should classify this.
break;

default:
Expand Down
17 changes: 15 additions & 2 deletions src/services/completions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -551,7 +551,7 @@ namespace ts.Completions {

if (keywordFilters !== KeywordCompletionFilters.None) {
const entryNames = new Set(entries.map(e => e.name));
for (const keywordEntry of getKeywordCompletions(keywordFilters, !insideJsDocTagTypeExpression && isSourceFileJS(sourceFile))) {
for (const keywordEntry of getKeywordCompletions(keywordFilters, !insideJsDocTagTypeExpression && isSourceFileJS(sourceFile) && !isInTypeAnnotationComment(sourceFile, position))) {
if (!entryNames.has(keywordEntry.name)) {
insertSorted(entries, keywordEntry, compareCompletionEntries, /*allowDuplicates*/ true);
}
Expand Down Expand Up @@ -579,6 +579,17 @@ namespace ts.Completions {
};
}

function isInTypeAnnotationComment(sourceFile: SourceFile, position: number): boolean {
return isInTypeAnnotationCommentFromTokens(getTokenAtPosition(sourceFile, position), getRelevantTokens(position, sourceFile));
}

function isInTypeAnnotationCommentFromTokens(currentToken: Node, tokens: { contextToken?: Node; previousToken?: Node; }) {
return !!(currentToken.flags & NodeFlags.InTypeComment) ||
!!((tokens.previousToken?.flags || 0) & NodeFlags.InTypeComment) ||
((tokens.previousToken?.kind === SyntaxKind.SlashAsteriskColonToken || tokens.previousToken?.kind === SyntaxKind.ColonToken) &&
currentToken.kind === SyntaxKind.AsteriskSlashToken);
}

function isUncheckedFile(sourceFile: SourceFile, compilerOptions: CompilerOptions): boolean {
return isSourceFileJS(sourceFile) && !isCheckJsEnabledForFile(sourceFile, compilerOptions);
}
Expand Down Expand Up @@ -1800,10 +1811,10 @@ namespace ts.Completions {
start = timestamp();
// The decision to provide completion depends on the contextToken, which is determined through the previousToken.
// Note: 'previousToken' (and thus 'contextToken') can be undefined if we are the beginning of the file
const isJsOnlyLocation = !insideJsDocTagTypeExpression && isSourceFileJS(sourceFile);
const tokens = getRelevantTokens(position, sourceFile);
const previousToken = tokens.previousToken!;
let contextToken = tokens.contextToken!;
const isJsOnlyLocation = !insideJsDocTagTypeExpression && isSourceFileJS(sourceFile) && !isInTypeAnnotationCommentFromTokens(currentToken, tokens);
log("getCompletionData: Get previous token: " + (timestamp() - start));

// Find the node where completion is requested on.
Expand Down Expand Up @@ -2012,6 +2023,7 @@ namespace ts.Completions {
// global symbols in scope. These results should be valid for either language as
// the set of symbols that can be referenced from this location.
if (!tryGetGlobalSymbols()) {
log('ono tryGetGlobalSymbols false??');
return keywordFilters
? keywordCompletionData(keywordFilters, isJsOnlyLocation, isNewIdentifierLocation)
: undefined;
Expand Down Expand Up @@ -2455,6 +2467,7 @@ namespace ts.Completions {
if (contextToken) {
const parentKind = contextToken.parent.kind;
switch (contextToken.kind) {
case SyntaxKind.SlashAsteriskColonToken:
case SyntaxKind.ColonToken:
return parentKind === SyntaxKind.PropertyDeclaration ||
parentKind === SyntaxKind.PropertySignature ||
Expand Down

0 comments on commit 49be504

Please sign in to comment.