@@ -59,6 +59,7 @@ import {
5959 isTextSpanInGeneratedCode ,
6060 SnapshotMap
6161} from './utils' ;
62+ import { Node } from 'vscode-html-languageservice' ;
6263
6364/**
6465 * TODO change this to protocol constant if it's part of the protocol
@@ -701,10 +702,8 @@ export class CodeActionsProviderImpl implements CodeActionsProvider {
701702 ) ,
702703 ...this . getSvelteQuickFixes (
703704 lang ,
704- document ,
705705 cannotFindNameDiagnostic ,
706706 tsDoc ,
707- formatCodeBasis ,
708707 userPreferences ,
709708 formatCodeSettings
710709 )
@@ -760,8 +759,18 @@ export class CodeActionsProviderImpl implements CodeActionsProvider {
760759 lang
761760 ) ;
762761
762+ const addLangCodeAction = this . getAddLangTSCodeAction (
763+ document ,
764+ context ,
765+ tsDoc ,
766+ formatCodeBasis
767+ ) ;
768+
763769 // filter out empty code action
764- return codeActionsNotFilteredOut . map ( ( { codeAction } ) => codeAction ) . concat ( fixAllActions ) ;
770+ const result = codeActionsNotFilteredOut
771+ . map ( ( { codeAction } ) => codeAction )
772+ . concat ( fixAllActions ) ;
773+ return addLangCodeAction ? [ addLangCodeAction ] . concat ( result ) : result ;
765774 }
766775
767776 private async convertAndFixCodeFixAction ( {
@@ -1128,10 +1137,8 @@ export class CodeActionsProviderImpl implements CodeActionsProvider {
11281137
11291138 private getSvelteQuickFixes (
11301139 lang : ts . LanguageService ,
1131- document : Document ,
11321140 cannotFindNameDiagnostics : Diagnostic [ ] ,
11331141 tsDoc : DocumentSnapshot ,
1134- formatCodeBasis : FormatCodeBasis ,
11351142 userPreferences : ts . UserPreferences ,
11361143 formatCodeSettings : ts . FormatCodeSettings
11371144 ) : CustomFixCannotFindNameInfo [ ] {
@@ -1141,14 +1148,10 @@ export class CodeActionsProviderImpl implements CodeActionsProvider {
11411148 return [ ] ;
11421149 }
11431150
1144- const typeChecker = program . getTypeChecker ( ) ;
11451151 const results : CustomFixCannotFindNameInfo [ ] = [ ] ;
1146- const quote = getQuotePreference ( sourceFile , userPreferences ) ;
11471152 const getGlobalCompletion = memoize ( ( ) =>
11481153 lang . getCompletionsAtPosition ( tsDoc . filePath , 0 , userPreferences , formatCodeSettings )
11491154 ) ;
1150- const [ tsMajorStr ] = ts . version . split ( '.' ) ;
1151- const tsSupportHandlerQuickFix = parseInt ( tsMajorStr ) >= 5 ;
11521155
11531156 for ( const diagnostic of cannotFindNameDiagnostics ) {
11541157 const identifier = this . findIdentifierForDiagnostic ( tsDoc , diagnostic , sourceFile ) ;
@@ -1173,24 +1176,6 @@ export class CodeActionsProviderImpl implements CodeActionsProvider {
11731176 ) ;
11741177 }
11751178
1176- if ( ! tsSupportHandlerQuickFix ) {
1177- const isQuickFixTargetEventHandler = this . isQuickFixForEventHandler (
1178- document ,
1179- diagnostic
1180- ) ;
1181- if ( isQuickFixTargetEventHandler ) {
1182- fixes . push (
1183- ...this . getEventHandlerQuickFixes (
1184- identifier ,
1185- tsDoc ,
1186- typeChecker ,
1187- quote ,
1188- formatCodeBasis
1189- )
1190- ) ;
1191- }
1192- }
1193-
11941179 if ( ! fixes . length ) {
11951180 continue ;
11961181 }
@@ -1225,8 +1210,6 @@ export class CodeActionsProviderImpl implements CodeActionsProvider {
12251210 return identifier ;
12261211 }
12271212
1228- // TODO: Remove this in late 2023
1229- // when most users have upgraded to TS 5.0+
12301213 private getSvelteStoreQuickFixes (
12311214 identifier : ts . Identifier ,
12321215 lang : ts . LanguageService ,
@@ -1275,101 +1258,121 @@ export class CodeActionsProviderImpl implements CodeActionsProvider {
12751258 return flatten ( completion . entries . filter ( ( c ) => c . name === storeIdentifier ) . map ( toFix ) ) ;
12761259 }
12771260
1278- /**
1279- * Workaround for TypeScript doesn't provide a quick fix if the signature is typed as union type, like `(() => void) | null`
1280- * We can remove this once TypeScript doesn't have this limitation.
1281- */
1282- private getEventHandlerQuickFixes (
1283- identifier : ts . Identifier ,
1261+ private getAddLangTSCodeAction (
1262+ document : Document ,
1263+ context : CodeActionContext ,
12841264 tsDoc : DocumentSnapshot ,
1285- typeChecker : ts . TypeChecker ,
1286- quote : string ,
12871265 formatCodeBasis : FormatCodeBasis
1288- ) : ts . CodeFixAction [ ] {
1289- const type = identifier && typeChecker . getContextualType ( identifier ) ;
1266+ ) {
1267+ if ( tsDoc . scriptKind !== ts . ScriptKind . JS ) {
1268+ return ;
1269+ }
12901270
1291- // if it's not union typescript should be able to do it. no need to enhance
1292- if ( ! type || ! type . isUnion ( ) ) {
1293- return [ ] ;
1271+ let hasTSOnlyDiagnostic = false ;
1272+ for ( const diagnostic of context . diagnostics ) {
1273+ const num = Number ( diagnostic . code ) ;
1274+ const canOnlyBeUsedInTS = num >= 8004 && num <= 8017 ;
1275+ if ( canOnlyBeUsedInTS ) {
1276+ hasTSOnlyDiagnostic = true ;
1277+ break ;
1278+ }
1279+ }
1280+ if ( ! hasTSOnlyDiagnostic ) {
1281+ return ;
12941282 }
12951283
1296- const nonNullable = type . getNonNullableType ( ) ;
1284+ if ( ! document . scriptInfo && ! document . moduleScriptInfo ) {
1285+ const hasNonTopLevelLang = document . html . roots . some ( ( node ) =>
1286+ this . hasLangTsScriptTag ( node )
1287+ ) ;
1288+ // Might be because issue with parsing the script tag, so don't suggest adding a new one
1289+ if ( hasNonTopLevelLang ) {
1290+ return ;
1291+ }
12971292
1298- if (
1299- ! (
1300- nonNullable . flags & ts . TypeFlags . Object &&
1301- ( nonNullable as ts . ObjectType ) . objectFlags & ts . ObjectFlags . Anonymous
1302- )
1303- ) {
1304- return [ ] ;
1293+ return CodeAction . create (
1294+ 'Add <script lang="ts"> tag' ,
1295+ {
1296+ documentChanges : [
1297+ {
1298+ textDocument : OptionalVersionedTextDocumentIdentifier . create (
1299+ document . uri ,
1300+ null
1301+ ) ,
1302+ edits : [
1303+ {
1304+ range : Range . create (
1305+ Position . create ( 0 , 0 ) ,
1306+ Position . create ( 0 , 0 )
1307+ ) ,
1308+ newText : '<script lang="ts"></script>' + formatCodeBasis . newLine
1309+ }
1310+ ]
1311+ }
1312+ ]
1313+ } ,
1314+ CodeActionKind . QuickFix
1315+ ) ;
13051316 }
13061317
1307- const signature = typeChecker . getSignaturesOfType ( nonNullable , ts . SignatureKind . Call ) [ 0 ] ;
1318+ const edits = [ document . scriptInfo , document . moduleScriptInfo ]
1319+ . map ( ( info ) => {
1320+ if ( ! info ) {
1321+ return ;
1322+ }
13081323
1309- const parameters = signature . parameters . map ( ( p ) => {
1310- const declaration = p . valueDeclaration ?? p . declarations ?. [ 0 ] ;
1311- const typeString = declaration
1312- ? typeChecker . typeToString ( typeChecker . getTypeOfSymbolAtLocation ( p , declaration ) )
1313- : '' ;
1324+ const startTagNameEnd = document . positionAt ( info . container . start + 7 ) ; // <script
1325+ const existingLangOffset = document
1326+ . getText ( {
1327+ start : startTagNameEnd ,
1328+ end : document . positionAt ( info . start )
1329+ } )
1330+ . indexOf ( 'lang=' ) ;
13141331
1315- return { name : p . name , typeString } ;
1316- } ) ;
1332+ if ( existingLangOffset !== - 1 ) {
1333+ return ;
1334+ }
13171335
1318- const returnType = typeChecker . typeToString ( signature . getReturnType ( ) ) ;
1319- const useJsDoc =
1320- tsDoc . scriptKind === ts . ScriptKind . JS || tsDoc . scriptKind === ts . ScriptKind . JSX ;
1321- const parametersText = (
1322- useJsDoc
1323- ? parameters . map ( ( p ) => p . name )
1324- : parameters . map ( ( p ) => p . name + ( p . typeString ? ': ' + p . typeString : '' ) )
1325- ) . join ( ', ' ) ;
1326-
1327- const jsDoc = useJsDoc
1328- ? [ '/**' , ...parameters . map ( ( p ) => ` * @param {${ p . typeString } } ${ p . name } ` ) , ' */' ]
1329- : [ ] ;
1330-
1331- const newText = [
1332- ...jsDoc ,
1333- `function ${ identifier . text } (${ parametersText } )${
1334- useJsDoc || returnType === 'any' ? '' : ': ' + returnType
1335- } {`,
1336- formatCodeBasis . indent +
1337- `throw new Error(${ quote } Function not implemented.${ quote } )` +
1338- formatCodeBasis . semi ,
1339- '}'
1340- ]
1341- . map ( ( line ) => formatCodeBasis . baseIndent + line + formatCodeBasis . newLine )
1342- . join ( '' ) ;
1336+ return {
1337+ range : Range . create ( startTagNameEnd , startTagNameEnd ) ,
1338+ newText : ' lang="ts"'
1339+ } ;
1340+ } )
1341+ . filter ( isNotNullOrUndefined ) ;
13431342
1344- return [
1345- {
1346- description : ` Add missing function declaration ' ${ identifier . text } '` ,
1347- fixName : 'fixMissingFunctionDeclaration' ,
1348- changes : [
1349- {
1350- fileName : tsDoc . filePath ,
1351- textChanges : [
1352- {
1353- newText ,
1354- span : { start : 0 , length : 0 }
1355- }
1356- ]
1357- }
1358- ]
1359- }
1360- ] ;
1343+ if ( edits . length ) {
1344+ return CodeAction . create (
1345+ ' Add lang="ts" to <script> tag' ,
1346+ {
1347+ documentChanges : [
1348+ {
1349+ textDocument : OptionalVersionedTextDocumentIdentifier . create (
1350+ document . uri ,
1351+ null
1352+ ) ,
1353+ edits
1354+ }
1355+ ]
1356+ } ,
1357+ CodeActionKind . QuickFix
1358+ ) ;
1359+ }
13611360 }
13621361
1363- private isQuickFixForEventHandler ( document : Document , diagnostic : Diagnostic ) {
1364- const htmlNode = document . html . findNodeAt ( document . offsetAt ( diagnostic . range . start ) ) ;
1362+ private hasLangTsScriptTag ( node : Node ) : boolean {
13651363 if (
1366- ! htmlNode . attributes ||
1367- ! Object . keys ( htmlNode . attributes ) . some ( ( attr ) => attr . startsWith ( 'on:' ) )
1364+ node . tag === 'script' &&
1365+ ( node . attributes ?. lang === '"ts"' || node . attributes ?. lang === "'ts'" ) &&
1366+ node . parent
13681367 ) {
1369- return false ;
1368+ return true ;
13701369 }
1371-
1372- return true ;
1370+ for ( const element of node . children ) {
1371+ if ( this . hasLangTsScriptTag ( element ) ) {
1372+ return true ;
1373+ }
1374+ }
1375+ return false ;
13731376 }
13741377
13751378 private async getApplicableRefactors (
0 commit comments