@@ -10,10 +10,11 @@ import {
10
10
} from '@microsoft/tsdoc-config' ;
11
11
import * as eslint from "eslint" ;
12
12
import * as ESTree from "estree" ;
13
+ import * as path from 'path' ;
13
14
14
15
const tsdocMessageIds : { [ x : string ] : string } = { } ;
15
16
16
- const defaultTSDocConfiguration : TSDocConfiguration = new TSDocConfiguration ( )
17
+ const defaultTSDocConfiguration : TSDocConfiguration = new TSDocConfiguration ( ) ;
17
18
defaultTSDocConfiguration . allTsdocMessageIds . forEach ( ( messageId : string ) => {
18
19
tsdocMessageIds [ messageId ] = `${ messageId } : {{unformattedText}}` ;
19
20
} ) ;
@@ -22,6 +23,33 @@ interface IPlugin {
22
23
rules : { [ x : string ] : eslint . Rule . RuleModule } ;
23
24
}
24
25
26
+ // To debug the plugin, temporarily uncomment the body of this function
27
+ function debug ( message : string ) : void {
28
+ message = require ( "process" ) . pid + ": " + message ;
29
+ console . log ( message ) ;
30
+ require ( 'fs' ) . writeFileSync ( 'C:\\Git\\log.txt' , message + '\r\n' , { flag : 'as' } ) ;
31
+ }
32
+
33
+ interface ICachedConfig {
34
+ loadTimeMs : number ;
35
+ lastCheckTimeMs : number ;
36
+ configFile : TSDocConfigFile ;
37
+ }
38
+
39
+ // How often to check for modified input files. If a file's modification timestamp has changed, then we will
40
+ // evict the cache entry immediately.
41
+ const CACHE_CHECK_INTERVAL_MS : number = 15 * 1000 ;
42
+
43
+ // Evict old entries from the cache after this much time, regardless of whether the file was detected as being
44
+ // modified or not.
45
+ const CACHE_EXPIRE_MS : number = 30 * 1000 ;
46
+
47
+ // If this many objects accumulate in the cache, then it is cleared to avoid a memory leak.
48
+ const CACHE_MAX_SIZE : number = 100 ;
49
+
50
+ // findConfigPathForFolder() result --> loaded tsdoc.json configuration
51
+ const cachedConfigs : Map < string , ICachedConfig > = new Map < string , ICachedConfig > ( ) ;
52
+
25
53
const plugin : IPlugin = {
26
54
rules : {
27
55
// NOTE: The actual ESLint rule name will be "tsdoc/syntax". It is calculated by deleting "eslint-plugin-"
@@ -43,10 +71,67 @@ const plugin: IPlugin = {
43
71
}
44
72
} ,
45
73
create : ( context : eslint . Rule . RuleContext ) => {
46
- const tsdocConfiguration : TSDocConfiguration = new TSDocConfiguration ( ) ;
47
-
48
74
const sourceFilePath : string = context . getFilename ( ) ;
49
- const tsdocConfigFile : TSDocConfigFile = TSDocConfigFile . loadForFolder ( sourceFilePath ) ;
75
+
76
+ debug ( `Linting: "${ sourceFilePath } "` ) ;
77
+
78
+ // First, determine the file to be loaded. If not found, the configFilePath will be an empty string.
79
+ const configFilePath : string = '' ; // TSDocConfigFile.findConfigPathForFolder(sourceFilePath);
80
+
81
+ // If configFilePath is an empty string, then we'll use the folder of sourceFilePath as our cache key
82
+ // (instead of an empty string)
83
+ const cacheKey : string = configFilePath || path . dirname ( sourceFilePath ) ;
84
+ debug ( `Cache key: "${ cacheKey } "` ) ;
85
+
86
+ const nowMs : number = ( new Date ( ) ) . getTime ( ) ;
87
+
88
+ let cachedConfig : ICachedConfig | undefined = undefined ;
89
+
90
+ // Do we have a cached object?
91
+ cachedConfig = cachedConfigs . get ( cacheKey ) ;
92
+
93
+ if ( cachedConfig ) {
94
+ debug ( 'Cache hit' ) ;
95
+
96
+ // Is the cached object still valid?
97
+ const loadAgeMs : number = nowMs - cachedConfig . loadTimeMs ;
98
+ const lastCheckAgeMs : number = nowMs - cachedConfig . lastCheckTimeMs ;
99
+
100
+ if ( loadAgeMs > CACHE_EXPIRE_MS || loadAgeMs < 0 ) {
101
+ debug ( 'Evicting because item is expired' ) ;
102
+ cachedConfig = undefined ;
103
+ cachedConfigs . delete ( cacheKey ) ;
104
+ } else if ( lastCheckAgeMs > CACHE_CHECK_INTERVAL_MS || lastCheckAgeMs < 0 ) {
105
+ debug ( 'Checking for modifications' ) ;
106
+ cachedConfig . lastCheckTimeMs = nowMs ;
107
+ if ( cachedConfig . configFile . checkForModifiedFiles ( ) ) {
108
+ // Invalidate the cache because it failed to load completely
109
+ debug ( 'Evicting because item was modified' ) ;
110
+ cachedConfig = undefined ;
111
+ cachedConfigs . delete ( cacheKey ) ;
112
+ }
113
+ }
114
+ }
115
+
116
+ // Load the object
117
+ if ( ! cachedConfig ) {
118
+ if ( cachedConfigs . size > CACHE_MAX_SIZE ) {
119
+ debug ( 'Clearing cache' ) ;
120
+ cachedConfigs . clear ( ) ; // avoid a memory leak
121
+ }
122
+
123
+ debug ( `LOADING CONFIG: ${ configFilePath } ` ) ;
124
+ cachedConfig = {
125
+ configFile : TSDocConfigFile . loadFile ( configFilePath ) ,
126
+ lastCheckTimeMs : nowMs ,
127
+ loadTimeMs : nowMs
128
+ } ;
129
+
130
+ cachedConfigs . set ( cacheKey , cachedConfig ) ;
131
+ }
132
+
133
+ const tsdocConfigFile : TSDocConfigFile = cachedConfig . configFile ;
134
+ const tsdocConfiguration : TSDocConfiguration = new TSDocConfiguration ( )
50
135
51
136
if ( ! tsdocConfigFile . fileNotFound ) {
52
137
if ( tsdocConfigFile . hasErrors ) {
0 commit comments