@@ -1312,7 +1312,7 @@ namespace ts {
1312
1312
*/
1313
1313
export function getDirectoryPath ( path : Path ) : Path ;
1314
1314
export function getDirectoryPath ( path : string ) : string ;
1315
- export function getDirectoryPath ( path : string ) : any {
1315
+ export function getDirectoryPath ( path : string ) : string {
1316
1316
return path . substr ( 0 , Math . max ( getRootLength ( path ) , path . lastIndexOf ( directorySeparator ) ) ) ;
1317
1317
}
1318
1318
@@ -1572,6 +1572,10 @@ namespace ts {
1572
1572
return expectedPos >= 0 && str . indexOf ( suffix , expectedPos ) === expectedPos ;
1573
1573
}
1574
1574
1575
+ export function hasExtension ( fileName : string ) : boolean {
1576
+ return getBaseFileName ( fileName ) . indexOf ( "." ) >= 0 ;
1577
+ }
1578
+
1575
1579
export function fileExtensionIs ( path : string , extension : string ) : boolean {
1576
1580
return path . length > extension . length && endsWith ( path , extension ) ;
1577
1581
}
@@ -1617,81 +1621,105 @@ namespace ts {
1617
1621
1618
1622
let pattern = "" ;
1619
1623
let hasWrittenSubpattern = false ;
1620
- spec: for ( const spec of specs ) {
1624
+ for ( const spec of specs ) {
1621
1625
if ( ! spec ) {
1622
1626
continue ;
1623
1627
}
1624
1628
1625
- let subpattern = "" ;
1626
- let hasRecursiveDirectoryWildcard = false ;
1627
- let hasWrittenComponent = false ;
1628
- const components = getNormalizedPathComponents ( spec , basePath ) ;
1629
- if ( usage !== "exclude" && components [ components . length - 1 ] === "**" ) {
1630
- continue spec;
1629
+ const subPattern = getSubPatternFromSpec ( spec , basePath , usage , singleAsteriskRegexFragment , doubleAsteriskRegexFragment , replaceWildcardCharacter ) ;
1630
+ if ( subPattern === undefined ) {
1631
+ continue ;
1631
1632
}
1632
1633
1633
- // getNormalizedPathComponents includes the separator for the root component.
1634
- // We need to remove to create our regex correctly.
1635
- components [ 0 ] = removeTrailingDirectorySeparator ( components [ 0 ] ) ;
1634
+ if ( hasWrittenSubpattern ) {
1635
+ pattern += "|" ;
1636
+ }
1636
1637
1637
- let optionalCount = 0 ;
1638
- for ( let component of components ) {
1639
- if ( component === "**" ) {
1640
- if ( hasRecursiveDirectoryWildcard ) {
1641
- continue spec;
1642
- }
1638
+ pattern += "(" + subPattern + ")" ;
1639
+ hasWrittenSubpattern = true ;
1640
+ }
1643
1641
1644
- subpattern += doubleAsteriskRegexFragment ;
1645
- hasRecursiveDirectoryWildcard = true ;
1646
- hasWrittenComponent = true ;
1647
- }
1648
- else {
1649
- if ( usage === "directories" ) {
1650
- subpattern += "(" ;
1651
- optionalCount ++ ;
1652
- }
1642
+ if ( ! pattern ) {
1643
+ return undefined ;
1644
+ }
1653
1645
1654
- if ( hasWrittenComponent ) {
1655
- subpattern += directorySeparator ;
1656
- }
1646
+ // If excluding, match "foo/bar/baz...", but if including, only allow "foo".
1647
+ const terminator = usage === "exclude" ? "($|/)" : "$" ;
1648
+ return `^(${ pattern } )${ terminator } ` ;
1649
+ }
1657
1650
1658
- if ( usage !== "exclude" ) {
1659
- // The * and ? wildcards should not match directories or files that start with . if they
1660
- // appear first in a component. Dotted directories and files can be included explicitly
1661
- // like so: **/.*/.*
1662
- if ( component . charCodeAt ( 0 ) === CharacterCodes . asterisk ) {
1663
- subpattern += "([^./]" + singleAsteriskRegexFragment + ")?" ;
1664
- component = component . substr ( 1 ) ;
1665
- }
1666
- else if ( component . charCodeAt ( 0 ) === CharacterCodes . question ) {
1667
- subpattern += "[^./]" ;
1668
- component = component . substr ( 1 ) ;
1669
- }
1670
- }
1651
+ /**
1652
+ * An "includes" path "foo" is implicitly a glob "foo/** /*" (without the space) if its last component has no extension,
1653
+ * and does not contain any glob characters itself.
1654
+ */
1655
+ export function isImplicitGlob ( lastPathComponent : string ) : boolean {
1656
+ return ! / [ . * ? ] / . test ( lastPathComponent ) ;
1657
+ }
1658
+
1659
+ function getSubPatternFromSpec ( spec : string , basePath : string , usage : "files" | "directories" | "exclude" , singleAsteriskRegexFragment : string , doubleAsteriskRegexFragment : string , replaceWildcardCharacter : ( match : string ) => string ) : string | undefined {
1660
+ let subpattern = "" ;
1661
+ let hasRecursiveDirectoryWildcard = false ;
1662
+ let hasWrittenComponent = false ;
1663
+ const components = getNormalizedPathComponents ( spec , basePath ) ;
1664
+ const lastComponent = lastOrUndefined ( components ) ;
1665
+ if ( usage !== "exclude" && lastComponent === "**" ) {
1666
+ return undefined ;
1667
+ }
1668
+
1669
+ // getNormalizedPathComponents includes the separator for the root component.
1670
+ // We need to remove to create our regex correctly.
1671
+ components [ 0 ] = removeTrailingDirectorySeparator ( components [ 0 ] ) ;
1671
1672
1672
- subpattern += component . replace ( reservedCharacterPattern , replaceWildcardCharacter ) ;
1673
- hasWrittenComponent = true ;
1673
+ if ( isImplicitGlob ( lastComponent ) ) {
1674
+ components . push ( "**" , "*" ) ;
1675
+ }
1676
+
1677
+ let optionalCount = 0 ;
1678
+ for ( let component of components ) {
1679
+ if ( component === "**" ) {
1680
+ if ( hasRecursiveDirectoryWildcard ) {
1681
+ return undefined ;
1674
1682
}
1675
- }
1676
1683
1677
- while ( optionalCount > 0 ) {
1678
- subpattern += ")?" ;
1679
- optionalCount -- ;
1684
+ subpattern += doubleAsteriskRegexFragment ;
1685
+ hasRecursiveDirectoryWildcard = true ;
1680
1686
}
1687
+ else {
1688
+ if ( usage === "directories" ) {
1689
+ subpattern += "(" ;
1690
+ optionalCount ++ ;
1691
+ }
1681
1692
1682
- if ( hasWrittenSubpattern ) {
1683
- pattern += "|" ;
1693
+ if ( hasWrittenComponent ) {
1694
+ subpattern += directorySeparator ;
1695
+ }
1696
+
1697
+ if ( usage !== "exclude" ) {
1698
+ // The * and ? wildcards should not match directories or files that start with . if they
1699
+ // appear first in a component. Dotted directories and files can be included explicitly
1700
+ // like so: **/.*/.*
1701
+ if ( component . charCodeAt ( 0 ) === CharacterCodes . asterisk ) {
1702
+ subpattern += "([^./]" + singleAsteriskRegexFragment + ")?" ;
1703
+ component = component . substr ( 1 ) ;
1704
+ }
1705
+ else if ( component . charCodeAt ( 0 ) === CharacterCodes . question ) {
1706
+ subpattern += "[^./]" ;
1707
+ component = component . substr ( 1 ) ;
1708
+ }
1709
+ }
1710
+
1711
+ subpattern += component . replace ( reservedCharacterPattern , replaceWildcardCharacter ) ;
1684
1712
}
1685
1713
1686
- pattern += "(" + subpattern + ")" ;
1687
- hasWrittenSubpattern = true ;
1714
+ hasWrittenComponent = true ;
1688
1715
}
1689
1716
1690
- if ( ! pattern ) {
1691
- return undefined ;
1717
+ while ( optionalCount > 0 ) {
1718
+ subpattern += ")?" ;
1719
+ optionalCount -- ;
1692
1720
}
1693
1721
1694
- return "^(" + pattern + ( usage === "exclude" ? ")($|/)" : ")$" ) ;
1722
+ return subpattern ;
1695
1723
}
1696
1724
1697
1725
function replaceWildCardCharacterFiles ( match : string ) {
@@ -1778,43 +1806,44 @@ namespace ts {
1778
1806
function getBasePaths ( path : string , includes : string [ ] , useCaseSensitiveFileNames : boolean ) {
1779
1807
// Storage for our results in the form of literal paths (e.g. the paths as written by the user).
1780
1808
const basePaths : string [ ] = [ path ] ;
1809
+
1781
1810
if ( includes ) {
1782
1811
// Storage for literal base paths amongst the include patterns.
1783
1812
const includeBasePaths : string [ ] = [ ] ;
1784
1813
for ( const include of includes ) {
1785
1814
// We also need to check the relative paths by converting them to absolute and normalizing
1786
1815
// in case they escape the base path (e.g "..\somedirectory")
1787
1816
const absolute : string = isRootedDiskPath ( include ) ? include : normalizePath ( combinePaths ( path , include ) ) ;
1788
-
1789
- const wildcardOffset = indexOfAnyCharCode ( absolute , wildcardCharCodes ) ;
1790
- const includeBasePath = wildcardOffset < 0
1791
- ? removeTrailingDirectorySeparator ( getDirectoryPath ( absolute ) )
1792
- : absolute . substring ( 0 , absolute . lastIndexOf ( directorySeparator , wildcardOffset ) ) ;
1793
-
1794
1817
// Append the literal and canonical candidate base paths.
1795
- includeBasePaths . push ( includeBasePath ) ;
1818
+ includeBasePaths . push ( getIncludeBasePath ( absolute ) ) ;
1796
1819
}
1797
1820
1798
1821
// Sort the offsets array using either the literal or canonical path representations.
1799
1822
includeBasePaths . sort ( useCaseSensitiveFileNames ? compareStrings : compareStringsCaseInsensitive ) ;
1800
1823
1801
1824
// Iterate over each include base path and include unique base paths that are not a
1802
1825
// subpath of an existing base path
1803
- include: for ( let i = 0 ; i < includeBasePaths . length ; i ++ ) {
1804
- const includeBasePath = includeBasePaths [ i ] ;
1805
- for ( let j = 0 ; j < basePaths . length ; j ++ ) {
1806
- if ( containsPath ( basePaths [ j ] , includeBasePath , path , ! useCaseSensitiveFileNames ) ) {
1807
- continue include;
1808
- }
1826
+ for ( const includeBasePath of includeBasePaths ) {
1827
+ if ( ts . every ( basePaths , basePath => ! containsPath ( basePath , includeBasePath , path , ! useCaseSensitiveFileNames ) ) ) {
1828
+ basePaths . push ( includeBasePath ) ;
1809
1829
}
1810
-
1811
- basePaths . push ( includeBasePath ) ;
1812
1830
}
1813
1831
}
1814
1832
1815
1833
return basePaths ;
1816
1834
}
1817
1835
1836
+ function getIncludeBasePath ( absolute : string ) : string {
1837
+ const wildcardOffset = indexOfAnyCharCode ( absolute , wildcardCharCodes ) ;
1838
+ if ( wildcardOffset < 0 ) {
1839
+ // No "*" or "?" in the path
1840
+ return ! hasExtension ( absolute )
1841
+ ? absolute
1842
+ : removeTrailingDirectorySeparator ( getDirectoryPath ( absolute ) ) ;
1843
+ }
1844
+ return absolute . substring ( 0 , absolute . lastIndexOf ( directorySeparator , wildcardOffset ) ) ;
1845
+ }
1846
+
1818
1847
export function ensureScriptKind ( fileName : string , scriptKind ?: ScriptKind ) : ScriptKind {
1819
1848
// Using scriptKind as a condition handles both:
1820
1849
// - 'scriptKind' is unspecified and thus it is `undefined`
0 commit comments