@@ -1040,6 +1040,8 @@ namespace ts {
10401040 getFormattingEditsForDocument ( fileName : string , options : FormatCodeOptions ) : TextChange [ ] ;
10411041 getFormattingEditsAfterKeystroke ( fileName : string , position : number , key : string , options : FormatCodeOptions ) : TextChange [ ] ;
10421042
1043+ getDocCommentTemplateAtPosition ( fileName : string , position : number ) : TextInsertion ;
1044+
10431045 getEmitOutput ( fileName : string ) : EmitOutput ;
10441046
10451047 getProgram ( ) : Program ;
@@ -1086,6 +1088,12 @@ namespace ts {
10861088 newText : string ;
10871089 }
10881090
1091+ export interface TextInsertion {
1092+ newText : string ;
1093+ /** The position in newText the caret should point to after the insertion. */
1094+ caretOffset : number ;
1095+ }
1096+
10891097 export interface RenameLocation {
10901098 textSpan : TextSpan ;
10911099 fileName : string ;
@@ -5486,7 +5494,7 @@ namespace ts {
54865494 symbolToIndex : number [ ] ) : void {
54875495
54885496 let sourceFile = container . getSourceFile ( ) ;
5489- let tripleSlashDirectivePrefixRegex = / ^ \/ \/ \/ \s * < /
5497+ let tripleSlashDirectivePrefixRegex = / ^ \/ \/ \/ \s * < / ;
54905498
54915499 let possiblePositions = getPossibleSymbolReferencePositions ( sourceFile , searchText , container . getStart ( ) , container . getEnd ( ) ) ;
54925500
@@ -5502,8 +5510,8 @@ namespace ts {
55025510 // This wasn't the start of a token. Check to see if it might be a
55035511 // match in a comment or string if that's what the caller is asking
55045512 // for.
5505- if ( ( findInStrings && isInString ( position ) ) ||
5506- ( findInComments && isInComment ( position ) ) ) {
5513+ if ( ( findInStrings && isInString ( sourceFile , position ) ) ||
5514+ ( findInComments && isInNonReferenceComment ( sourceFile , position ) ) ) {
55075515
55085516 // In the case where we're looking inside comments/strings, we don't have
55095517 // an actual definition. So just use 'undefined' here. Features like
@@ -5567,30 +5575,13 @@ namespace ts {
55675575 return result [ index ] ;
55685576 }
55695577
5570- function isInString ( position : number ) {
5571- let token = getTokenAtPosition ( sourceFile , position ) ;
5572- return token && token . kind === SyntaxKind . StringLiteral && position > token . getStart ( ) ;
5573- }
5578+ function isInNonReferenceComment ( sourceFile : SourceFile , position : number ) : boolean {
5579+ return isInCommentHelper ( sourceFile , position , isNonReferenceComment ) ;
55745580
5575- function isInComment ( position : number ) {
5576- let token = getTokenAtPosition ( sourceFile , position ) ;
5577- if ( token && position < token . getStart ( ) ) {
5578- // First, we have to see if this position actually landed in a comment.
5579- let commentRanges = getLeadingCommentRanges ( sourceFile . text , token . pos ) ;
5580-
5581- // Then we want to make sure that it wasn't in a "///<" directive comment
5582- // We don't want to unintentionally update a file name.
5583- return forEach ( commentRanges , c => {
5584- if ( c . pos < position && position < c . end ) {
5585- let commentText = sourceFile . text . substring ( c . pos , c . end ) ;
5586- if ( ! tripleSlashDirectivePrefixRegex . test ( commentText ) ) {
5587- return true ;
5588- }
5589- }
5590- } ) ;
5581+ function isNonReferenceComment ( c : CommentRange ) : boolean {
5582+ let commentText = sourceFile . text . substring ( c . pos , c . end ) ;
5583+ return ! tripleSlashDirectivePrefixRegex . test ( commentText ) ;
55915584 }
5592-
5593- return false ;
55945585 }
55955586 }
55965587
@@ -6817,6 +6808,78 @@ namespace ts {
68176808 return [ ] ;
68186809 }
68196810
6811+ /**
6812+ * Checks if position points to a valid position to add JSDoc comments, and if so,
6813+ * returns the appropriate template. Otherwise returns an empty string.
6814+ * Valid positions are
6815+ * - outside of comments, statements, and expressions, and
6816+ * - preceding a function declaration.
6817+ *
6818+ * Hosts should ideally check that:
6819+ * - The line is all whitespace up to 'position' before performing the insertion.
6820+ * - If the keystroke sequence "/\*\*" induced the call, we also check that the next
6821+ * non-whitespace character is '*', which (approximately) indicates whether we added
6822+ * the second '*' to complete an existing (JSDoc) comment.
6823+ * @param fileName The file in which to perform the check.
6824+ * @param position The (character-indexed) position in the file where the check should
6825+ * be performed.
6826+ */
6827+ function getDocCommentTemplateAtPosition ( fileName : string , position : number ) : TextInsertion {
6828+ let start = new Date ( ) . getTime ( ) ;
6829+ let sourceFile = syntaxTreeCache . getCurrentSourceFile ( fileName ) ;
6830+
6831+ // Check if in a context where we don't want to perform any insertion
6832+ if ( isInString ( sourceFile , position ) || isInComment ( sourceFile , position ) || hasDocComment ( sourceFile , position ) ) {
6833+ return undefined ;
6834+ }
6835+
6836+ let tokenAtPos = getTokenAtPosition ( sourceFile , position ) ;
6837+ let tokenStart = tokenAtPos . getStart ( )
6838+ if ( ! tokenAtPos || tokenStart < position ) {
6839+ return undefined ;
6840+ }
6841+
6842+ // TODO: add support for:
6843+ // - methods
6844+ // - constructors
6845+ // - class decls
6846+ let containingFunction = < FunctionDeclaration > getAncestor ( tokenAtPos , SyntaxKind . FunctionDeclaration ) ;
6847+
6848+ if ( ! containingFunction || containingFunction . getStart ( ) < position ) {
6849+ return undefined ;
6850+ }
6851+
6852+ let parameters = containingFunction . parameters ;
6853+ let posLineAndChar = sourceFile . getLineAndCharacterOfPosition ( position ) ;
6854+ let lineStart = sourceFile . getLineStarts ( ) [ posLineAndChar . line ] ;
6855+
6856+ let indentationStr = sourceFile . text . substr ( lineStart , posLineAndChar . character ) ;
6857+
6858+ // TODO: call a helper method instead once PR #4133 gets merged in.
6859+ const newLine = host . getNewLine ? host . getNewLine ( ) : "\r\n" ;
6860+
6861+ let docParams = parameters . reduce ( ( prev , cur , index ) =>
6862+ prev +
6863+ indentationStr + " * @param " + ( cur . name . kind === SyntaxKind . Identifier ? ( < Identifier > cur . name ) . text : "param" + index ) + newLine , "" ) ;
6864+
6865+ // A doc comment consists of the following
6866+ // * The opening comment line
6867+ // * the first line (without a param) for the object's untagged info (this is also where the caret ends up)
6868+ // * the '@param'-tagged lines
6869+ // * TODO: other tags.
6870+ // * the closing comment line
6871+ // * if the caret was directly in front of the object, then we add an extra line and indentation.
6872+ const preamble = "/**" + newLine +
6873+ indentationStr + " * " ;
6874+ let result =
6875+ preamble + newLine +
6876+ docParams +
6877+ indentationStr + " */" +
6878+ ( tokenStart === position ? newLine + indentationStr : "" ) ;
6879+
6880+ return { newText : result , caretOffset : preamble . length } ;
6881+ }
6882+
68206883 function getTodoComments ( fileName : string , descriptors : TodoCommentDescriptor [ ] ) : TodoComment [ ] {
68216884 // Note: while getting todo comments seems like a syntactic operation, we actually
68226885 // treat it as a semantic operation here. This is because we expect our host to call
@@ -7058,6 +7121,7 @@ namespace ts {
70587121 getFormattingEditsForRange,
70597122 getFormattingEditsForDocument,
70607123 getFormattingEditsAfterKeystroke,
7124+ getDocCommentTemplateAtPosition,
70617125 getEmitOutput,
70627126 getSourceFile,
70637127 getProgram
0 commit comments