diff --git a/packages/nx/src/utils/strip-source-code.spec.ts b/packages/nx/src/utils/strip-source-code.spec.ts index 082783b45a41b..2d287e061089d 100644 --- a/packages/nx/src/utils/strip-source-code.spec.ts +++ b/packages/nx/src/utils/strip-source-code.spec.ts @@ -258,6 +258,20 @@ require('./d')`; expect(stripSourceCode(scanner, input)).toEqual(expected); }); + it('should find an import after a template literal with a 2 variables in it', () => { + const input = ` + const a = 1; + const b = 2; + const c = \`a: $\{a}, b: $\{b}\` + const d = await import('./d') + const e = require('./e') + `; + const expected = `import('./d') +require('./e')`; + + expect(stripSourceCode(scanner, input)).toEqual(expected); + }); + it('finds imports after an escaped character', () => { const input = ` const b = unquotedLiteral.replace(/"/g, '\\\\"') @@ -282,4 +296,37 @@ require('./d')`; expect(stripSourceCode(scanner, input)).toEqual(expected); }); + + it('finds imports in the same line as template literals with division inside', () => { + const input = ` + const a = 1; + const b = \`"$\{1 / 2} $\{await import('./b')} $\{await require('./c')}"\`; + `; + const expected = `import('./b') +require('./c')`; + + expect(stripSourceCode(scanner, input)).toEqual(expected); + }); + + it('finds imports in the same line after a regex', () => { + const input = ` + const a = 1; + const b = /"/g; const c = await import('./c'); const d = require('./d') + `; + const expected = `import('./c') +require('./d')`; + + expect(stripSourceCode(scanner, input)).toEqual(expected); + }); + + it('finds imports inside template literals', () => { + const input = ` + const a = \`"$\{require('./a')}"\` + const b = \`"$\{await import('./b')}"\` + `; + const expected = `require('./a') +import('./b')`; + + expect(stripSourceCode(scanner, input)).toEqual(expected); + }); }); diff --git a/packages/nx/src/utils/strip-source-code.ts b/packages/nx/src/utils/strip-source-code.ts index 0e85d38c3d224..22cccb516c157 100644 --- a/packages/nx/src/utils/strip-source-code.ts +++ b/packages/nx/src/utils/strip-source-code.ts @@ -1,6 +1,29 @@ import type { Scanner } from 'typescript'; let SyntaxKind: typeof import('typescript').SyntaxKind; +function shouldRescanSlashToken( + lastNonTriviaToken: import('typescript').SyntaxKind +) { + switch (lastNonTriviaToken) { + case SyntaxKind.Identifier: + case SyntaxKind.StringLiteral: + case SyntaxKind.NumericLiteral: + case SyntaxKind.BigIntLiteral: + case SyntaxKind.RegularExpressionLiteral: + case SyntaxKind.ThisKeyword: + case SyntaxKind.PlusPlusToken: + case SyntaxKind.MinusMinusToken: + case SyntaxKind.CloseParenToken: + case SyntaxKind.CloseBracketToken: + case SyntaxKind.CloseBraceToken: + case SyntaxKind.TrueKeyword: + case SyntaxKind.FalseKeyword: + return false; + default: + return true; + } +} + export function stripSourceCode(scanner: Scanner, contents: string): string { if (!SyntaxKind) { SyntaxKind = require('typescript').SyntaxKind; @@ -12,9 +35,14 @@ export function stripSourceCode(scanner: Scanner, contents: string): string { scanner.setText(contents); let token = scanner.scan(); + let lastNonTriviaToken = SyntaxKind.Unknown; const statements = []; + const templateStack = []; + let ignoringLine = false; + let braceDepth = 0; let start = null; while (token !== SyntaxKind.EndOfFileToken) { + const currentToken = token; const potentialStart = scanner.getStartPos(); switch (token) { case SyntaxKind.MultiLineCommentTrivia: @@ -33,21 +61,23 @@ export function stripSourceCode(scanner: Scanner, contents: string): string { ) { token = scanner.scan(); } - - // ignore next line - while ( - token !== SyntaxKind.NewLineTrivia && - token !== SyntaxKind.EndOfFileToken - ) { - token = scanner.scan(); - } + ignoringLine = true; } break; } + case SyntaxKind.NewLineTrivia: { + ignoringLine = false; + token = scanner.scan(); + break; + } + case SyntaxKind.RequireKeyword: case SyntaxKind.ImportKeyword: { token = scanner.scan(); + if (ignoringLine) { + break; + } while ( token === SyntaxKind.WhitespaceTrivia || token === SyntaxKind.NewLineTrivia @@ -59,29 +89,44 @@ export function stripSourceCode(scanner: Scanner, contents: string): string { } case SyntaxKind.TemplateHead: { - while (true) { - token = scanner.scan(); + templateStack.push(braceDepth); + braceDepth = 0; + token = scanner.scan(); + break; + } - if (token === SyntaxKind.SlashToken) { - token = scanner.reScanSlashToken(); - } + case SyntaxKind.SlashToken: { + if (shouldRescanSlashToken(lastNonTriviaToken)) { + token = scanner.reScanSlashToken(); + } + token = scanner.scan(); + break; + } - if (token === SyntaxKind.EndOfFileToken) { - // either the template is unterminated, or there - // is some other edge case we haven't compensated for - break; - } + case SyntaxKind.OpenBraceToken: { + ++braceDepth; + token = scanner.scan(); + break; + } - if (token === SyntaxKind.CloseBraceToken) { - token = scanner.reScanTemplateToken(false); - break; + case SyntaxKind.CloseBraceToken: { + if (braceDepth) { + --braceDepth; + } else if (templateStack.length) { + token = scanner.reScanTemplateToken(false); + if (token === SyntaxKind.LastTemplateToken) { + braceDepth = templateStack.pop(); } } + token = scanner.scan(); break; } case SyntaxKind.ExportKeyword: { token = scanner.scan(); + if (ignoringLine) { + break; + } while ( token === SyntaxKind.WhitespaceTrivia || token === SyntaxKind.NewLineTrivia @@ -117,6 +162,10 @@ export function stripSourceCode(scanner: Scanner, contents: string): string { token = scanner.scan(); } } + + if (currentToken > SyntaxKind.LastTriviaToken) { + lastNonTriviaToken = currentToken; + } } return statements.join('\n');