@@ -114,6 +114,7 @@ export namespace TestLint {
114114 export interface IRunLintOptions {
115115 name : string ;
116116 source : string ;
117+ /** Optional disposable root under the OS temp dir; cleanup removes it. */
117118 projectRoot ?: string ;
118119 rules ?: Record < string , LintRuleConfigSeverity > ;
119120 pluginConfig ?: Record < string , unknown > ;
@@ -168,6 +169,9 @@ export namespace TestLint {
168169 const tmpdir =
169170 projectRoot ??
170171 TestProject . tmpdir ( `ttsc-lint-case-${ sanitizeForFsName ( name ) } -` ) ;
172+ if ( projectRoot !== undefined ) {
173+ assertDisposableProjectRoot ( projectRoot ) ;
174+ }
171175 try {
172176 // The tsconfig plugin entry never carries rules: it is empty, or
173177 // optionally names a config file via `configFile`. When a test uses the
@@ -273,6 +277,21 @@ export namespace TestLint {
273277 ) ;
274278 }
275279
280+ function assertDisposableProjectRoot ( projectRoot : string ) : void {
281+ const tempRoot = path . resolve ( os . tmpdir ( ) ) ;
282+ const resolved = path . resolve ( projectRoot ) ;
283+ const relative = path . relative ( tempRoot , resolved ) ;
284+ if (
285+ relative === "" ||
286+ ( ! relative . startsWith ( ".." ) && ! path . isAbsolute ( relative ) )
287+ ) {
288+ return ;
289+ }
290+ throw new Error (
291+ `TestLint projectRoot must be a disposable directory under ${ tempRoot } : ${ projectRoot } ` ,
292+ ) ;
293+ }
294+
276295 /** Link the workspace @ttsc/lint package as if the fixture had installed it. */
277296 function seedNodeModulesLink ( tmpdir : string ) : void {
278297 const linkParent = path . join ( tmpdir , "node_modules" , "@ttsc" ) ;
0 commit comments