From 1481024bb45c3a1d3928942542d09e186e707810 Mon Sep 17 00:00:00 2001 From: Paul Cowan Date: Fri, 16 Aug 2019 17:21:45 +0100 Subject: [PATCH 1/2] create Logger for writing exceptions to the ts server log --- src/helpers/Logger.ts | 19 ++++ src/helpers/__tests__/createMatchers.test.ts | 6 +- src/helpers/__tests__/cssSnapshots.test.ts | 11 +- src/helpers/config.ts | 1 + src/helpers/createMatchers.ts | 4 +- src/helpers/cssSnapshots.ts | 110 ++++++++++--------- src/index.ts | 20 +++- 7 files changed, 109 insertions(+), 62 deletions(-) create mode 100644 src/helpers/Logger.ts create mode 100644 src/helpers/config.ts diff --git a/src/helpers/Logger.ts b/src/helpers/Logger.ts new file mode 100644 index 0000000..6f24c14 --- /dev/null +++ b/src/helpers/Logger.ts @@ -0,0 +1,19 @@ +import { pluginName } from './config'; + +export interface Logger { + log(msg: string): void; + error(e: Error): void; +} + +export class LanguageServiceLogger implements Logger { + constructor(private readonly info: ts.server.PluginCreateInfo) {} + + public log(msg: string) { + this.info.project.projectService.logger.info(`[${pluginName}] ${msg}`); + } + + public error(e: Error) { + this.log(`Failed ${e.toString()}`); + this.log(`Stack trace: ${e.stack}`); + } +} diff --git a/src/helpers/__tests__/createMatchers.test.ts b/src/helpers/__tests__/createMatchers.test.ts index 5eeb119..918f20a 100644 --- a/src/helpers/__tests__/createMatchers.test.ts +++ b/src/helpers/__tests__/createMatchers.test.ts @@ -1,10 +1,12 @@ import { createMatchers } from '../createMatchers'; import { Options } from '../../options'; +import { Logger } from '../Logger'; describe('utils / createMatchers', () => { + const logger: Logger = { log: jest.fn(), error: jest.fn() }; it('should match `customMatcher` regexp', () => { const options: Options = { customMatcher: '\\.css$' }; - const { isCSS, isRelativeCSS } = createMatchers(options); + const { isCSS, isRelativeCSS } = createMatchers(logger, options); expect(isCSS('./myfile.css')).toBe(true); expect(isCSS('./myfile.m.css')).toBe(true); @@ -16,7 +18,7 @@ describe('utils / createMatchers', () => { it('should handle bad `customMatcher` regexp', () => { const options: Options = { customMatcher: '$([a' }; - const { isCSS, isRelativeCSS } = createMatchers(options); + const { isCSS, isRelativeCSS } = createMatchers(logger, options); expect(isCSS('./myfile.module.css')).toBe(true); expect(isRelativeCSS('../folders/myfile.module.scss')).toBe(true); diff --git a/src/helpers/__tests__/cssSnapshots.test.ts b/src/helpers/__tests__/cssSnapshots.test.ts index 1fbe44a..62ddcb3 100644 --- a/src/helpers/__tests__/cssSnapshots.test.ts +++ b/src/helpers/__tests__/cssSnapshots.test.ts @@ -1,7 +1,7 @@ import { readFileSync } from 'fs'; import { IICSSExports } from 'icss-utils'; import { join } from 'path'; -import { createExports, getClasses, getFileType } from '../cssSnapshots'; +import { DtsSnapshotCreator } from '../cssSnapshots'; const testFileNames = [ 'test.module.css', @@ -14,11 +14,16 @@ const testFileNames = [ describe('utils / cssSnapshots', () => { testFileNames.forEach((fileName) => { let classes: IICSSExports; + let dtsSnapshotCreator: DtsSnapshotCreator; const fullFileName = join(__dirname, 'fixtures', fileName); const testFile = readFileSync(fullFileName, 'utf8'); beforeAll(() => { - classes = getClasses(testFile, fullFileName); + dtsSnapshotCreator = new DtsSnapshotCreator({ + log: jest.fn(), + error: jest.fn(), + }); + classes = dtsSnapshotCreator.getClasses(testFile, fullFileName); }); describe(`with file '${fileName}'`, () => { @@ -30,7 +35,7 @@ describe('utils / cssSnapshots', () => { describe('createExports', () => { it('should create an exports file', () => { - const exports = createExports(classes, {}); + const exports = dtsSnapshotCreator.createExports(classes, {}); expect(exports).toMatchSnapshot(); }); }); diff --git a/src/helpers/config.ts b/src/helpers/config.ts new file mode 100644 index 0000000..4dc12de --- /dev/null +++ b/src/helpers/config.ts @@ -0,0 +1 @@ +export const pluginName = 'typescript-plugin-css-modules'; diff --git a/src/helpers/createMatchers.ts b/src/helpers/createMatchers.ts index 1e0f0a6..a57e22b 100644 --- a/src/helpers/createMatchers.ts +++ b/src/helpers/createMatchers.ts @@ -1,7 +1,8 @@ import { createIsCSS, createIsRelativeCSS } from './cssExtensions'; import { Options } from '../options'; +import { Logger } from './Logger'; -export const createMatchers = (options: Options = {}) => { +export const createMatchers = (logger: Logger, options: Options = {}) => { // Allow custom matchers to be used, and handle bad matcher patterns. let isCSS = createIsCSS(); try { @@ -11,6 +12,7 @@ export const createMatchers = (options: Options = {}) => { isCSS = createIsCSS(customMatcherRegExp); } } catch (e) { + logger.error(e); // TODO: Provide error/warning to user. } diff --git a/src/helpers/cssSnapshots.ts b/src/helpers/cssSnapshots.ts index d2d77cf..32bfe69 100644 --- a/src/helpers/cssSnapshots.ts +++ b/src/helpers/cssSnapshots.ts @@ -7,6 +7,7 @@ import * as sass from 'sass'; import * as reserved from 'reserved-words'; import { transformClasses } from './classTransforms'; import { Options } from '../options'; +import { Logger } from './Logger'; const NOT_CAMELCASE_REGEXP = /[\-_]/; const processor = postcss(postcssIcssSelectors({ mode: 'local' })); @@ -35,69 +36,74 @@ export const getFileType = (fileName: string) => { const getFilePath = (fileName: string) => fileName.substring(0, fileName.lastIndexOf('/')); -export const getClasses = (css: string, fileName: string) => { - try { - const fileType = getFileType(fileName); - let transformedCss = ''; - - if (fileType === FileTypes.less) { - less.render(css, { asyncImport: true } as any, (err, output) => { - transformedCss = output.css.toString(); - }); - } else if (fileType === FileTypes.scss) { - const filePath = getFilePath(fileName); - transformedCss = sass - .renderSync({ - data: css, - includePaths: [filePath], - }) - .css.toString(); - } else { - transformedCss = css; - } +export class DtsSnapshotCreator { + constructor(private readonly logger: Logger) {} + + getClasses(css: string, fileName: string) { + try { + const fileType = getFileType(fileName); + let transformedCss = ''; + + if (fileType === FileTypes.less) { + less.render(css, { asyncImport: true } as any, (err, output) => { + transformedCss = output.css.toString(); + }); + } else if (fileType === FileTypes.scss) { + const filePath = getFilePath(fileName); + transformedCss = sass + .renderSync({ + data: css, + includePaths: [filePath], + }) + .css.toString(); + } else { + transformedCss = css; + } - const processedCss = processor.process(transformedCss); + const processedCss = processor.process(transformedCss); - return extractICSS(processedCss.root).icssExports; - } catch (e) { - return {}; + return extractICSS(processedCss.root).icssExports; + } catch (e) { + this.logger.error(e); + return {}; + } } -}; -export const createExports = (classes: IICSSExports, options: Options) => { - const isCamelCase = (className: string) => - !NOT_CAMELCASE_REGEXP.test(className); - const isReservedWord = (className: string) => !reserved.check(className); + createExports(classes: IICSSExports, options: Options) { + const isCamelCase = (className: string) => + !NOT_CAMELCASE_REGEXP.test(className); + const isReservedWord = (className: string) => !reserved.check(className); - const processedClasses = Object.keys(classes) - .map(transformClasses(options.camelCase)) - .reduce(flattenClassNames, []); - const camelCasedKeys = processedClasses - .filter(isCamelCase) - .filter(isReservedWord) - .map(classNameToNamedExport); + const processedClasses = Object.keys(classes) + .map(transformClasses(options.camelCase)) + .reduce(flattenClassNames, []); + const camelCasedKeys = processedClasses + .filter(isCamelCase) + .filter(isReservedWord) + .map(classNameToNamedExport); - const defaultExport = `\ + const defaultExport = `\ declare const classes: { ${processedClasses.map(classNameToProperty).join('\n ')} }; export default classes; `; - if (camelCasedKeys.length) { - return defaultExport + camelCasedKeys.join('\n') + '\n'; + if (camelCasedKeys.length) { + return defaultExport + camelCasedKeys.join('\n') + '\n'; + } + return defaultExport; } - return defaultExport; -}; -export const getDtsSnapshot = ( - ts: typeof ts_module, - fileName: string, - scriptSnapshot: ts.IScriptSnapshot, - options: Options, -) => { - const css = scriptSnapshot.getText(0, scriptSnapshot.getLength()); - const classes = getClasses(css, fileName); - const dts = createExports(classes, options); - return ts.ScriptSnapshot.fromString(dts); -}; + getDtsSnapshot( + ts: typeof ts_module, + fileName: string, + scriptSnapshot: ts.IScriptSnapshot, + options: Options, + ) { + const css = scriptSnapshot.getText(0, scriptSnapshot.getLength()); + const classes = this.getClasses(css, fileName); + const dts = this.createExports(classes, options); + return ts.ScriptSnapshot.fromString(dts); + } +} diff --git a/src/index.ts b/src/index.ts index 20352c7..3b507d7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,17 +3,23 @@ import * as path from 'path'; import * as ts_module from 'typescript/lib/tsserverlibrary'; import { createMatchers } from './helpers/createMatchers'; import { isCSSFn } from './helpers/cssExtensions'; -import { getDtsSnapshot } from './helpers/cssSnapshots'; +import { DtsSnapshotCreator } from './helpers/cssSnapshots'; import { Options } from './options'; +import { LanguageServiceLogger } from './helpers/Logger'; function init({ typescript: ts }: { typescript: typeof ts_module }) { let _isCSS: isCSSFn; function create(info: ts.server.PluginCreateInfo) { + const logger = new LanguageServiceLogger(info); + const dtsSnapshotCreator = new DtsSnapshotCreator(logger); + // User options for plugin. const options: Options = info.config.options || {}; + logger.log(`options: ${JSON.stringify(options)}`); + // Create matchers using options object. - const { isCSS, isRelativeCSS } = createMatchers(options); + const { isCSS, isRelativeCSS } = createMatchers(logger, options); _isCSS = isCSS; // Creates new virtual source files for the CSS modules. @@ -24,7 +30,12 @@ function init({ typescript: ts }: { typescript: typeof ts_module }) { ...rest ): ts.SourceFile => { if (isCSS(fileName)) { - scriptSnapshot = getDtsSnapshot(ts, fileName, scriptSnapshot, options); + scriptSnapshot = dtsSnapshotCreator.getDtsSnapshot( + ts, + fileName, + scriptSnapshot, + options, + ); } const sourceFile = _createLanguageServiceSourceFile( fileName, @@ -45,7 +56,7 @@ function init({ typescript: ts }: { typescript: typeof ts_module }) { ...rest ): ts.SourceFile => { if (isCSS(sourceFile.fileName)) { - scriptSnapshot = getDtsSnapshot( + scriptSnapshot = dtsSnapshotCreator.getDtsSnapshot( ts, sourceFile.fileName, scriptSnapshot, @@ -132,6 +143,7 @@ function init({ typescript: ts }: { typescript: typeof ts_module }) { } } } catch (e) { + logger.error(e); return resolvedModules[index]; } return resolvedModules[index]; From 39b4afd2505977189262f002a4583d91a9d987ed Mon Sep 17 00:00:00 2001 From: Paul Cowan Date: Fri, 16 Aug 2019 17:33:26 +0100 Subject: [PATCH 2/2] rename cssSnapshots.ts => DtsSnapshotCreator --- src/helpers/{cssSnapshots.ts => DtsSnapshotCreator.ts} | 0 .../{cssSnapshots.test.ts => DtsSnapshotCreator.test.ts} | 2 +- ...ssSnapshots.test.ts.snap => DtsSnapshotCreator.test.ts.snap} | 0 src/index.ts | 2 +- 4 files changed, 2 insertions(+), 2 deletions(-) rename src/helpers/{cssSnapshots.ts => DtsSnapshotCreator.ts} (100%) rename src/helpers/__tests__/{cssSnapshots.test.ts => DtsSnapshotCreator.test.ts} (95%) rename src/helpers/__tests__/__snapshots__/{cssSnapshots.test.ts.snap => DtsSnapshotCreator.test.ts.snap} (100%) diff --git a/src/helpers/cssSnapshots.ts b/src/helpers/DtsSnapshotCreator.ts similarity index 100% rename from src/helpers/cssSnapshots.ts rename to src/helpers/DtsSnapshotCreator.ts diff --git a/src/helpers/__tests__/cssSnapshots.test.ts b/src/helpers/__tests__/DtsSnapshotCreator.test.ts similarity index 95% rename from src/helpers/__tests__/cssSnapshots.test.ts rename to src/helpers/__tests__/DtsSnapshotCreator.test.ts index 62ddcb3..647e5a9 100644 --- a/src/helpers/__tests__/cssSnapshots.test.ts +++ b/src/helpers/__tests__/DtsSnapshotCreator.test.ts @@ -1,7 +1,7 @@ import { readFileSync } from 'fs'; import { IICSSExports } from 'icss-utils'; import { join } from 'path'; -import { DtsSnapshotCreator } from '../cssSnapshots'; +import { DtsSnapshotCreator } from '../DtsSnapshotCreator'; const testFileNames = [ 'test.module.css', diff --git a/src/helpers/__tests__/__snapshots__/cssSnapshots.test.ts.snap b/src/helpers/__tests__/__snapshots__/DtsSnapshotCreator.test.ts.snap similarity index 100% rename from src/helpers/__tests__/__snapshots__/cssSnapshots.test.ts.snap rename to src/helpers/__tests__/__snapshots__/DtsSnapshotCreator.test.ts.snap diff --git a/src/index.ts b/src/index.ts index 3b507d7..b119e26 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,7 +3,7 @@ import * as path from 'path'; import * as ts_module from 'typescript/lib/tsserverlibrary'; import { createMatchers } from './helpers/createMatchers'; import { isCSSFn } from './helpers/cssExtensions'; -import { DtsSnapshotCreator } from './helpers/cssSnapshots'; +import { DtsSnapshotCreator } from './helpers/DtsSnapshotCreator'; import { Options } from './options'; import { LanguageServiceLogger } from './helpers/Logger';