@@ -1177,7 +1177,7 @@ namespace ts {
11771177
11781178 /**
11791179 * Returns the path except for its basename. Eg:
1180- *
1180+ *
11811181 * /path/to/file.ext -> /path/to
11821182 */
11831183 export function getDirectoryPath ( path : Path ) : Path ;
@@ -1186,6 +1186,12 @@ namespace ts {
11861186 return path . substr ( 0 , Math . max ( getRootLength ( path ) , path . lastIndexOf ( directorySeparator ) ) ) ;
11871187 }
11881188
1189+ function getBasename ( path : Path ) : Path ;
1190+ function getBasename ( path : string ) : string ;
1191+ function getBasename ( path : string ) : any {
1192+ return path . substr ( Math . max ( getRootLength ( path ) , path . lastIndexOf ( directorySeparator ) ) ) ;
1193+ }
1194+
11891195 export function isUrl ( path : string ) {
11901196 return path && ! isRootedDiskPath ( path ) && path . indexOf ( "://" ) !== - 1 ;
11911197 }
@@ -1476,7 +1482,7 @@ namespace ts {
14761482 return undefined ;
14771483 }
14781484
1479- const replaceWildcardCharacter = usage === "files" ? replaceWildCardCharacterFiles : replaceWildCardCharacterOther ;
1485+ const replaceWildcardCharacter = usage === "files" ? replaceWildCardCharacterFiles : replaceWildCardCharacterOther ;
14801486 const singleAsteriskRegexFragment = usage === "files" ? singleAsteriskRegexFragmentFiles : singleAsteriskRegexFragmentOther ;
14811487
14821488 /**
@@ -1487,81 +1493,103 @@ namespace ts {
14871493
14881494 let pattern = "" ;
14891495 let hasWrittenSubpattern = false ;
1490- spec: for ( const spec of specs ) {
1496+ for ( const spec of specs ) {
14911497 if ( ! spec ) {
14921498 continue ;
14931499 }
14941500
1495- let subpattern = "" ;
1496- let hasRecursiveDirectoryWildcard = false ;
1497- let hasWrittenComponent = false ;
1498- const components = getNormalizedPathComponents ( spec , basePath ) ;
1499- if ( usage !== "exclude" && components [ components . length - 1 ] === "**" ) {
1500- continue spec;
1501+ const subPattern = getSubPatternFromSpec ( spec , basePath , usage , singleAsteriskRegexFragment , doubleAsteriskRegexFragment , replaceWildcardCharacter ) ;
1502+ if ( subPattern === undefined ) {
1503+ continue ;
15011504 }
15021505
1503- // getNormalizedPathComponents includes the separator for the root component.
1504- // We need to remove to create our regex correctly.
1505- components [ 0 ] = removeTrailingDirectorySeparator ( components [ 0 ] ) ;
1506+ if ( hasWrittenSubpattern ) {
1507+ pattern += "|" ;
1508+ }
15061509
1507- let optionalCount = 0 ;
1508- for ( let component of components ) {
1509- if ( component === "**" ) {
1510- if ( hasRecursiveDirectoryWildcard ) {
1511- continue spec;
1512- }
1510+ pattern += "(" + subPattern + ")" ;
1511+ hasWrittenSubpattern = true ;
1512+ }
15131513
1514- subpattern += doubleAsteriskRegexFragment ;
1515- hasRecursiveDirectoryWildcard = true ;
1516- hasWrittenComponent = true ;
1517- }
1518- else {
1519- if ( usage === "directories" ) {
1520- subpattern += "(" ;
1521- optionalCount ++ ;
1522- }
1514+ if ( ! pattern ) {
1515+ return undefined ;
1516+ }
15231517
1524- if ( hasWrittenComponent ) {
1525- subpattern += directorySeparator ;
1526- }
1518+ return "^(" + pattern + ( usage === "exclude" ? ")($|/)" : ")$" ) ;
1519+ }
15271520
1528- if ( usage !== "exclude" ) {
1529- // The * and ? wildcards should not match directories or files that start with . if they
1530- // appear first in a component. Dotted directories and files can be included explicitly
1531- // like so: **/.*/.*
1532- if ( component . charCodeAt ( 0 ) === CharacterCodes . asterisk ) {
1533- subpattern += "([^./]" + singleAsteriskRegexFragment + ")?" ;
1534- component = component . substr ( 1 ) ;
1535- }
1536- else if ( component . charCodeAt ( 0 ) === CharacterCodes . question ) {
1537- subpattern += "[^./]" ;
1538- component = component . substr ( 1 ) ;
1539- }
1540- }
1521+ /**
1522+ * An "includes" path "foo" is implicitly a glob "foo\**\*" (replace \ with /) if its last component has no extension,
1523+ * and does not contain any glob characters itself.
1524+ */
1525+ export function isImplicitGlob ( lastPathComponent : string ) : boolean {
1526+ return ! / [ . * ? ] / . test ( lastPathComponent ) ;
1527+ }
1528+
1529+ function getSubPatternFromSpec ( spec : string , basePath : string , usage : "files" | "directories" | "exclude" , singleAsteriskRegexFragment : string , doubleAsteriskRegexFragment : string , replaceWildcardCharacter : ( match : string ) => string ) : string | undefined {
1530+ let subpattern = "" ;
1531+ let hasRecursiveDirectoryWildcard = false ;
1532+ let hasWrittenComponent = false ;
1533+ const components = getNormalizedPathComponents ( spec , basePath ) ;
1534+ const lastComponent = lastOrUndefined ( components ) ;
1535+ if ( usage !== "exclude" && lastComponent === "**" ) {
1536+ return undefined ;
1537+ }
1538+
1539+ // getNormalizedPathComponents includes the separator for the root component.
1540+ // We need to remove to create our regex correctly.
1541+ components [ 0 ] = removeTrailingDirectorySeparator ( components [ 0 ] ) ;
15411542
1542- subpattern += component . replace ( reservedCharacterPattern , replaceWildcardCharacter ) ;
1543- hasWrittenComponent = true ;
1543+ if ( isImplicitGlob ( lastComponent ) ) {
1544+ components . push ( "**" , "*" ) ;
1545+ }
1546+
1547+ let optionalCount = 0 ;
1548+ for ( let component of components ) {
1549+ if ( component === "**" ) {
1550+ if ( hasRecursiveDirectoryWildcard ) {
1551+ return undefined ;
15441552 }
1545- }
15461553
1547- while ( optionalCount > 0 ) {
1548- subpattern += ")?" ;
1549- optionalCount -- ;
1554+ subpattern += doubleAsteriskRegexFragment ;
1555+ hasRecursiveDirectoryWildcard = true ;
15501556 }
1557+ else {
1558+ if ( usage === "directories" ) {
1559+ subpattern += "(" ;
1560+ optionalCount ++ ;
1561+ }
15511562
1552- if ( hasWrittenSubpattern ) {
1553- pattern += "|" ;
1563+ if ( hasWrittenComponent ) {
1564+ subpattern += directorySeparator ;
1565+ }
1566+
1567+ if ( usage !== "exclude" ) {
1568+ // The * and ? wildcards should not match directories or files that start with . if they
1569+ // appear first in a component. Dotted directories and files can be included explicitly
1570+ // like so: **/.*/.*
1571+ if ( component . charCodeAt ( 0 ) === CharacterCodes . asterisk ) {
1572+ subpattern += "([^./]" + singleAsteriskRegexFragment + ")?" ;
1573+ component = component . substr ( 1 ) ;
1574+ }
1575+ else if ( component . charCodeAt ( 0 ) === CharacterCodes . question ) {
1576+ subpattern += "[^./]" ;
1577+ component = component . substr ( 1 ) ;
1578+ }
1579+ }
1580+
1581+ subpattern += component . replace ( reservedCharacterPattern , replaceWildcardCharacter ) ;
15541582 }
15551583
1556- pattern += "(" + subpattern + ")" ;
1557- hasWrittenSubpattern = true ;
1584+ hasWrittenComponent = true ;
15581585 }
15591586
1560- if ( ! pattern ) {
1561- return undefined ;
1587+ while ( optionalCount > 0 ) {
1588+ subpattern += ")?" ;
1589+ optionalCount -- ;
15621590 }
15631591
1564- return "^(" + pattern + ( usage === "exclude" ? ")($|/)" : ")$" ) ;
1592+ return subpattern ;
15651593 }
15661594
15671595 function replaceWildCardCharacterFiles ( match : string ) {
@@ -1648,43 +1676,46 @@ namespace ts {
16481676 function getBasePaths ( path : string , includes : string [ ] , useCaseSensitiveFileNames : boolean ) {
16491677 // Storage for our results in the form of literal paths (e.g. the paths as written by the user).
16501678 const basePaths : string [ ] = [ path ] ;
1679+
16511680 if ( includes ) {
16521681 // Storage for literal base paths amongst the include patterns.
16531682 const includeBasePaths : string [ ] = [ ] ;
16541683 for ( const include of includes ) {
16551684 // We also need to check the relative paths by converting them to absolute and normalizing
16561685 // in case they escape the base path (e.g "..\somedirectory")
16571686 const absolute : string = isRootedDiskPath ( include ) ? include : normalizePath ( combinePaths ( path , include ) ) ;
1658-
1659- const wildcardOffset = indexOfAnyCharCode ( absolute , wildcardCharCodes ) ;
1660- const includeBasePath = wildcardOffset < 0
1661- ? removeTrailingDirectorySeparator ( getDirectoryPath ( absolute ) )
1662- : absolute . substring ( 0 , absolute . lastIndexOf ( directorySeparator , wildcardOffset ) ) ;
1663-
16641687 // Append the literal and canonical candidate base paths.
1665- includeBasePaths . push ( includeBasePath ) ;
1688+ includeBasePaths . push ( getIncludeBasePath ( absolute ) ) ;
16661689 }
16671690
16681691 // Sort the offsets array using either the literal or canonical path representations.
16691692 includeBasePaths . sort ( useCaseSensitiveFileNames ? compareStrings : compareStringsCaseInsensitive ) ;
16701693
16711694 // Iterate over each include base path and include unique base paths that are not a
16721695 // subpath of an existing base path
1673- include: for ( let i = 0 ; i < includeBasePaths . length ; i ++ ) {
1674- const includeBasePath = includeBasePaths [ i ] ;
1675- for ( let j = 0 ; j < basePaths . length ; j ++ ) {
1676- if ( containsPath ( basePaths [ j ] , includeBasePath , path , ! useCaseSensitiveFileNames ) ) {
1677- continue include;
1678- }
1696+ for ( const includeBasePath of includeBasePaths ) {
1697+ if ( ts . every ( basePaths , basePath => ! containsPath ( basePath , includeBasePath , path , ! useCaseSensitiveFileNames ) ) ) {
1698+ basePaths . push ( includeBasePath ) ;
16791699 }
1680-
1681- basePaths . push ( includeBasePath ) ;
16821700 }
16831701 }
16841702
16851703 return basePaths ;
16861704 }
16871705
1706+ function getIncludeBasePath ( absolute : string ) : string {
1707+ const wildcardOffset = indexOfAnyCharCode ( absolute , wildcardCharCodes ) ;
1708+ if ( wildcardOffset < 0 ) {
1709+ // No "*" in the path
1710+ return isImplicitGlob ( getBasename ( absolute ) )
1711+ ? absolute
1712+ : removeTrailingDirectorySeparator ( getDirectoryPath ( absolute ) ) ;
1713+ }
1714+ else {
1715+ return absolute . substring ( 0 , absolute . lastIndexOf ( directorySeparator , wildcardOffset ) ) ;
1716+ }
1717+ }
1718+
16881719 export function ensureScriptKind ( fileName : string , scriptKind ?: ScriptKind ) : ScriptKind {
16891720 // Using scriptKind as a condition handles both:
16901721 // - 'scriptKind' is unspecified and thus it is `undefined`
0 commit comments