Skip to content

Commit 143b7ea

Browse files
dagda1mrmckeb
authored andcommitted
Add TypeScript server logs for exceptions (#38)
1 parent cfdaca2 commit 143b7ea

File tree

9 files changed

+160
-113
lines changed

9 files changed

+160
-113
lines changed

src/helpers/DtsSnapshotCreator.ts

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import { extractICSS, IICSSExports } from 'icss-utils';
2+
import * as postcss from 'postcss';
3+
import * as postcssIcssSelectors from 'postcss-icss-selectors';
4+
import * as ts_module from 'typescript/lib/tsserverlibrary';
5+
import * as less from 'less';
6+
import * as sass from 'sass';
7+
import * as reserved from 'reserved-words';
8+
import { transformClasses } from './classTransforms';
9+
import { Options } from '../options';
10+
import { Logger } from './Logger';
11+
12+
const NOT_CAMELCASE_REGEXP = /[\-_]/;
13+
const processor = postcss(postcssIcssSelectors({ mode: 'local' }));
14+
15+
const classNameToProperty = (className: string) => `'${className}': string;`;
16+
const classNameToNamedExport = (className: string) =>
17+
`export const ${className}: string;`;
18+
19+
const flattenClassNames = (
20+
previousValue: string[] = [],
21+
currentValue: string[],
22+
) => previousValue.concat(currentValue);
23+
24+
export const enum FileTypes {
25+
css = 'css',
26+
less = 'less',
27+
scss = 'scss',
28+
}
29+
30+
export const getFileType = (fileName: string) => {
31+
if (fileName.endsWith('.css')) return FileTypes.css;
32+
if (fileName.endsWith('.less')) return FileTypes.less;
33+
return FileTypes.scss;
34+
};
35+
36+
const getFilePath = (fileName: string) =>
37+
fileName.substring(0, fileName.lastIndexOf('/'));
38+
39+
export class DtsSnapshotCreator {
40+
constructor(private readonly logger: Logger) {}
41+
42+
getClasses(css: string, fileName: string) {
43+
try {
44+
const fileType = getFileType(fileName);
45+
let transformedCss = '';
46+
47+
if (fileType === FileTypes.less) {
48+
less.render(css, { asyncImport: true } as any, (err, output) => {
49+
transformedCss = output.css.toString();
50+
});
51+
} else if (fileType === FileTypes.scss) {
52+
const filePath = getFilePath(fileName);
53+
transformedCss = sass
54+
.renderSync({
55+
data: css,
56+
includePaths: [filePath],
57+
})
58+
.css.toString();
59+
} else {
60+
transformedCss = css;
61+
}
62+
63+
const processedCss = processor.process(transformedCss);
64+
65+
return extractICSS(processedCss.root).icssExports;
66+
} catch (e) {
67+
this.logger.error(e);
68+
return {};
69+
}
70+
}
71+
72+
createExports(classes: IICSSExports, options: Options) {
73+
const isCamelCase = (className: string) =>
74+
!NOT_CAMELCASE_REGEXP.test(className);
75+
const isReservedWord = (className: string) => !reserved.check(className);
76+
77+
const processedClasses = Object.keys(classes)
78+
.map(transformClasses(options.camelCase))
79+
.reduce(flattenClassNames, []);
80+
const camelCasedKeys = processedClasses
81+
.filter(isCamelCase)
82+
.filter(isReservedWord)
83+
.map(classNameToNamedExport);
84+
85+
const defaultExport = `\
86+
declare const classes: {
87+
${processedClasses.map(classNameToProperty).join('\n ')}
88+
};
89+
export default classes;
90+
`;
91+
92+
if (camelCasedKeys.length) {
93+
return defaultExport + camelCasedKeys.join('\n') + '\n';
94+
}
95+
return defaultExport;
96+
}
97+
98+
getDtsSnapshot(
99+
ts: typeof ts_module,
100+
fileName: string,
101+
scriptSnapshot: ts.IScriptSnapshot,
102+
options: Options,
103+
) {
104+
const css = scriptSnapshot.getText(0, scriptSnapshot.getLength());
105+
const classes = this.getClasses(css, fileName);
106+
const dts = this.createExports(classes, options);
107+
return ts.ScriptSnapshot.fromString(dts);
108+
}
109+
}

src/helpers/Logger.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { pluginName } from './config';
2+
3+
export interface Logger {
4+
log(msg: string): void;
5+
error(e: Error): void;
6+
}
7+
8+
export class LanguageServiceLogger implements Logger {
9+
constructor(private readonly info: ts.server.PluginCreateInfo) {}
10+
11+
public log(msg: string) {
12+
this.info.project.projectService.logger.info(`[${pluginName}] ${msg}`);
13+
}
14+
15+
public error(e: Error) {
16+
this.log(`Failed ${e.toString()}`);
17+
this.log(`Stack trace: ${e.stack}`);
18+
}
19+
}

src/helpers/__tests__/cssSnapshots.test.ts renamed to src/helpers/__tests__/DtsSnapshotCreator.test.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { readFileSync } from 'fs';
22
import { IICSSExports } from 'icss-utils';
33
import { join } from 'path';
4-
import { createExports, getClasses, getFileType } from '../cssSnapshots';
4+
import { DtsSnapshotCreator } from '../DtsSnapshotCreator';
55

66
const testFileNames = [
77
'test.module.css',
@@ -14,11 +14,16 @@ const testFileNames = [
1414
describe('utils / cssSnapshots', () => {
1515
testFileNames.forEach((fileName) => {
1616
let classes: IICSSExports;
17+
let dtsSnapshotCreator: DtsSnapshotCreator;
1718
const fullFileName = join(__dirname, 'fixtures', fileName);
1819
const testFile = readFileSync(fullFileName, 'utf8');
1920

2021
beforeAll(() => {
21-
classes = getClasses(testFile, fullFileName);
22+
dtsSnapshotCreator = new DtsSnapshotCreator({
23+
log: jest.fn(),
24+
error: jest.fn(),
25+
});
26+
classes = dtsSnapshotCreator.getClasses(testFile, fullFileName);
2227
});
2328

2429
describe(`with file '${fileName}'`, () => {
@@ -30,7 +35,7 @@ describe('utils / cssSnapshots', () => {
3035

3136
describe('createExports', () => {
3237
it('should create an exports file', () => {
33-
const exports = createExports(classes, {});
38+
const exports = dtsSnapshotCreator.createExports(classes, {});
3439
expect(exports).toMatchSnapshot();
3540
});
3641
});

src/helpers/__tests__/createMatchers.test.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { createMatchers } from '../createMatchers';
22
import { Options } from '../../options';
3+
import { Logger } from '../Logger';
34

45
describe('utils / createMatchers', () => {
6+
const logger: Logger = { log: jest.fn(), error: jest.fn() };
57
it('should match `customMatcher` regexp', () => {
68
const options: Options = { customMatcher: '\\.css$' };
7-
const { isCSS, isRelativeCSS } = createMatchers(options);
9+
const { isCSS, isRelativeCSS } = createMatchers(logger, options);
810

911
expect(isCSS('./myfile.css')).toBe(true);
1012
expect(isCSS('./myfile.m.css')).toBe(true);
@@ -16,7 +18,7 @@ describe('utils / createMatchers', () => {
1618

1719
it('should handle bad `customMatcher` regexp', () => {
1820
const options: Options = { customMatcher: '$([a' };
19-
const { isCSS, isRelativeCSS } = createMatchers(options);
21+
const { isCSS, isRelativeCSS } = createMatchers(logger, options);
2022

2123
expect(isCSS('./myfile.module.css')).toBe(true);
2224
expect(isRelativeCSS('../folders/myfile.module.scss')).toBe(true);

src/helpers/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const pluginName = 'typescript-plugin-css-modules';

src/helpers/createMatchers.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { createIsCSS, createIsRelativeCSS } from './cssExtensions';
22
import { Options } from '../options';
3+
import { Logger } from './Logger';
34

4-
export const createMatchers = (options: Options = {}) => {
5+
export const createMatchers = (logger: Logger, options: Options = {}) => {
56
// Allow custom matchers to be used, and handle bad matcher patterns.
67
let isCSS = createIsCSS();
78
try {
@@ -11,6 +12,7 @@ export const createMatchers = (options: Options = {}) => {
1112
isCSS = createIsCSS(customMatcherRegExp);
1213
}
1314
} catch (e) {
15+
logger.error(e);
1416
// TODO: Provide error/warning to user.
1517
}
1618

src/helpers/cssSnapshots.ts

Lines changed: 0 additions & 103 deletions
This file was deleted.

src/index.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,23 @@ import * as path from 'path';
33
import * as ts_module from 'typescript/lib/tsserverlibrary';
44
import { createMatchers } from './helpers/createMatchers';
55
import { isCSSFn } from './helpers/cssExtensions';
6-
import { getDtsSnapshot } from './helpers/cssSnapshots';
6+
import { DtsSnapshotCreator } from './helpers/DtsSnapshotCreator';
77
import { Options } from './options';
8+
import { LanguageServiceLogger } from './helpers/Logger';
89

910
function init({ typescript: ts }: { typescript: typeof ts_module }) {
1011
let _isCSS: isCSSFn;
1112
function create(info: ts.server.PluginCreateInfo) {
13+
const logger = new LanguageServiceLogger(info);
14+
const dtsSnapshotCreator = new DtsSnapshotCreator(logger);
15+
1216
// User options for plugin.
1317
const options: Options = info.config.options || {};
1418

19+
logger.log(`options: ${JSON.stringify(options)}`);
20+
1521
// Create matchers using options object.
16-
const { isCSS, isRelativeCSS } = createMatchers(options);
22+
const { isCSS, isRelativeCSS } = createMatchers(logger, options);
1723
_isCSS = isCSS;
1824

1925
// Creates new virtual source files for the CSS modules.
@@ -24,7 +30,12 @@ function init({ typescript: ts }: { typescript: typeof ts_module }) {
2430
...rest
2531
): ts.SourceFile => {
2632
if (isCSS(fileName)) {
27-
scriptSnapshot = getDtsSnapshot(ts, fileName, scriptSnapshot, options);
33+
scriptSnapshot = dtsSnapshotCreator.getDtsSnapshot(
34+
ts,
35+
fileName,
36+
scriptSnapshot,
37+
options,
38+
);
2839
}
2940
const sourceFile = _createLanguageServiceSourceFile(
3041
fileName,
@@ -45,7 +56,7 @@ function init({ typescript: ts }: { typescript: typeof ts_module }) {
4556
...rest
4657
): ts.SourceFile => {
4758
if (isCSS(sourceFile.fileName)) {
48-
scriptSnapshot = getDtsSnapshot(
59+
scriptSnapshot = dtsSnapshotCreator.getDtsSnapshot(
4960
ts,
5061
sourceFile.fileName,
5162
scriptSnapshot,
@@ -132,6 +143,7 @@ function init({ typescript: ts }: { typescript: typeof ts_module }) {
132143
}
133144
}
134145
} catch (e) {
146+
logger.error(e);
135147
return resolvedModules[index];
136148
}
137149
return resolvedModules[index];

0 commit comments

Comments
 (0)