Skip to content

Commit

Permalink
Add rule for testing the files property in package.json - fixes #3
Browse files Browse the repository at this point in the history
  • Loading branch information
SamVerschueren committed Sep 5, 2018
1 parent b97ac4a commit 0bc1413
Show file tree
Hide file tree
Showing 13 changed files with 180 additions and 25 deletions.
35 changes: 30 additions & 5 deletions source/lib/compiler.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import {createProgram, getPreEmitDiagnostics, ScriptTarget, ModuleResolutionKind} from 'typescript';
import * as path from 'path';
import {createProgram, getPreEmitDiagnostics, ScriptTarget, ModuleResolutionKind, flattenDiagnosticMessageText} from 'typescript';
import {Diagnostic, Context} from './interfaces';

const loadConfig = () => {
return {
Expand All @@ -8,15 +10,38 @@ const loadConfig = () => {
};

/**
* Get a list of diagnostics for the given file.
* Get a list of TypeScript diagnostics within the current context.
*
* @param fileName - Name of the file run the diagnosis on.
* @param context - The context object.
* @returns List of diagnostics
*/
export const getDiagnostics = (fileName: string) => {
export const getDiagnostics = (context: Context): Diagnostic[] => {
const compilerOptions = loadConfig();

const fileName = path.join(context.cwd, context.testFile);

const program = createProgram([fileName], compilerOptions);

return getPreEmitDiagnostics(program);
// Retrieve the TypeScript compiler diagnostics
const diagnostics = getPreEmitDiagnostics(program);

const result: Diagnostic[] = [];

for (const diagnostic of diagnostics) {
if (!diagnostic.file) {
continue;
}

const position = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start as number);

result.push({
fileName: diagnostic.file.fileName,
message: flattenDiagnosticMessageText(diagnostic.messageText, '\n'),
severity: 'error',
line: position.line + 1,
column: position.character
});
}

return result;
};
21 changes: 5 additions & 16 deletions source/lib/formatter.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {flattenDiagnosticMessageText, Diagnostic} from 'typescript';
import * as formatter from 'eslint-formatter-pretty';
import {Diagnostic} from './interfaces';

/**
* Format the TypeScript diagnostics to a human readable output.
Expand All @@ -11,32 +11,21 @@ export default (diagnostics: Diagnostic[]) => {
const fileMap = new Map<string>();

for (const diagnostic of diagnostics) {
if (!diagnostic.file) {
continue;
}

let entry = fileMap.get(diagnostic.file.fileName);
let entry = fileMap.get(diagnostic.fileName);

if (!entry) {
entry = {
filePath: diagnostic.file.fileName,
filePath: diagnostic.fileName,
errorCount: 0,
warningCount: 0,
messages: []
};

fileMap.set(diagnostic.file.fileName, entry);
fileMap.set(diagnostic.fileName, entry);
}

const position = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start as number);

entry.errorCount++;
entry.messages.push({
message: flattenDiagnosticMessageText(diagnostic.messageText, '\n'),
severity: 'error',
line: position.line + 1,
column: position.character
});
entry.messages.push(diagnostic);
}

return formatter(Array.from(fileMap.values()));
Expand Down
16 changes: 14 additions & 2 deletions source/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import * as path from 'path';
import * as readPkgUp from 'read-pkg-up';
import * as pathExists from 'path-exists';
import {getDiagnostics} from './compiler';
import {getDiagnostics as getTSDiagnostics} from './compiler';
import getCustomDiagnostics from './rules';
import {Context} from './interfaces';

interface Options {
cwd: string;
Expand Down Expand Up @@ -48,5 +50,15 @@ export default async (options: Options = {cwd: process.cwd()}) => {

const testFile = await findTestFile(typingsFile, options);

return getDiagnostics(path.join(options.cwd, testFile));
const context: Context = {
cwd: options.cwd,
pkg,
typingsFile,
testFile
};

return [
...getCustomDiagnostics(context),
...getTSDiagnostics(context)
];
};
14 changes: 14 additions & 0 deletions source/lib/interfaces.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export interface Context {
cwd: string;
pkg: any;
typingsFile: string;
testFile: string;
}

export interface Diagnostic {
fileName: string;
message: string;
severity: 'error' | 'warning';
line?: number;
column?: number;
}
30 changes: 30 additions & 0 deletions source/lib/rules/files-property.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import * as path from 'path';
import * as fs from 'fs';
import {Context, Diagnostic} from '../interfaces';
import {getJSONPropertyPosition} from '../utils';

/**
* Rule which enforces the typings file to be present in the `files` list in `package.json`.
*
* @param context - The context object.
* @returns A list of custom diagnostics.
*/
export default (context: Context): Diagnostic[] => {
const {pkg, typingsFile} = context;
const typingsFileName = path.basename(typingsFile);

if (!Array.isArray(pkg.files) || pkg.files.indexOf(typingsFileName) !== -1) {
return [];
}

const content = fs.readFileSync(path.join(context.cwd, 'package.json'), 'utf8');

return [
{
fileName: 'package.json',
message: 'TypeScript type definition is not part of the `files` list.',
severity: 'error',
...getJSONPropertyPosition(content, 'files')
}
];
};
25 changes: 25 additions & 0 deletions source/lib/rules/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import filesProperty from './files-property';
import {Diagnostic, Context} from '../interfaces';

type RuleFunction = (context: Context) => Diagnostic[];

// List of custom rules
const rules = new Set<RuleFunction>([
filesProperty
]);

/**
* Get a list of custom diagnostics within the current context.
*
* @param context - The context object.
* @returns List of diagnostics
*/
export default (context: Context) => {
const diagnostics: Diagnostic[] = [];

for (const rule of rules) {
diagnostics.push(...rule(context));
}

return diagnostics;
};
22 changes: 22 additions & 0 deletions source/lib/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* Retrieve the line and column position of a property in a JSON document.
*
* @param content - Content of the JSON document.
* @param property - Property to search for.
* @returns Position of the property or `undefined` if the property could not be found.
*/
export const getJSONPropertyPosition = (content: string, property: string) => {
const match = new RegExp(`([\\s\\S]*?)"${property}"`, 'm').exec(content);

if (!match) {
return;
}

const lines = match[0].split('\n');
const lastLine = lines[lines.length - 1];

return {
line: lines.length,
column: lastLine ? lastLine.indexOf(`"${property}"`) : 0
};
};
6 changes: 6 additions & 0 deletions source/test/fixtures/no-files/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
declare const one: {
(foo: string, bar: string): string;
(foo: number, bar: number): number;
};

export default one;
3 changes: 3 additions & 0 deletions source/test/fixtures/no-files/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports.default = (foo, bar) => {
return foo + bar;
};
5 changes: 5 additions & 0 deletions source/test/fixtures/no-files/index.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import {expectType} from '../../..';
import one from '.';

expectType<string>(one('foo', 'bar'));
expectType<number>(one(1, 2));
6 changes: 6 additions & 0 deletions source/test/fixtures/no-files/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "foo",
"files": [
"index.js"
]
}
6 changes: 5 additions & 1 deletion source/test/fixtures/success/package.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
{
"name": "foo"
"name": "foo",
"files": [
"index.js",
"index.d.ts"
]
}
16 changes: 15 additions & 1 deletion source/test/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,21 @@ test('return diagnostics', async t => {
const diagnostics = await m({cwd: path.join(__dirname, 'fixtures/failure')});

t.true(diagnostics.length === 1);
t.is(diagnostics[0].messageText, 'Argument of type \'number\' is not assignable to parameter of type \'string\'.');
t.is(diagnostics[0].message, 'Argument of type \'number\' is not assignable to parameter of type \'string\'.');
});

test('fail if typings file is not part of `files` list', async t => {
const diagnostics = await m({cwd: path.join(__dirname, 'fixtures/no-files')});

t.deepEqual(diagnostics, [
{
fileName: 'package.json',
message: 'TypeScript type definition is not part of the `files` list.',
severity: 'error',
line: 3,
column: 1
}
]);
});

test('return no diagnostics', async t => {
Expand Down

0 comments on commit 0bc1413

Please sign in to comment.