Skip to content

Commit

Permalink
Merge branch 'master' into chore/reuse-resolver-config
Browse files Browse the repository at this point in the history
  • Loading branch information
roman-sainchuk authored Aug 22, 2022
2 parents 7c5416b + f83e050 commit c53c50d
Show file tree
Hide file tree
Showing 18 changed files with 296 additions and 67 deletions.
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
coverage/
dist/
packages/cli/lib/
packages/core/lib/
*snapshot.js
1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ module.exports = {
clearMocks: true,
restoreMocks: true,
preset: 'ts-jest',
testEnvironment: 'node',
collectCoverageFrom: [
'packages/*/src/**/*.ts',
'!packages/**/__tests__/**/*',
Expand Down
53 changes: 53 additions & 0 deletions packages/core/src/__tests__/logger-browser.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/**
* @jest-environment jsdom
*/

import * as colorette from 'colorette';
import { logger, colorize } from '../logger';

describe('Logger in Browser', () => {
it('should call "console.error"', () => {
const error = jest.spyOn(console, 'error').mockImplementation();

logger.error('error');

expect(error).toBeCalledTimes(1);
expect(error).toBeCalledWith('error');

error.mockRestore();
});

it('should call "console.log"', () => {
const log = jest.spyOn(console, 'log').mockImplementation();

logger.info('info');

expect(log).toBeCalledTimes(1);
expect(log).toBeCalledWith('info');

log.mockRestore();
});

it('should call "console.warn"', () => {
const warn = jest.spyOn(console, 'warn').mockImplementation();

logger.warn('warn');

expect(warn).toBeCalledTimes(1);
expect(warn).toBeCalledWith('warn');

warn.mockRestore();
});
});

describe('colorize in Browser', () => {
it('should not call original colorette lib', () => {
const color = 'cyan';
const spyingCyan = jest.spyOn(colorette, color);

const colorized = colorize.cyan(color);

expect(spyingCyan).not.toBeCalled();
expect(colorized).toEqual(color);
});
});
47 changes: 47 additions & 0 deletions packages/core/src/__tests__/logger.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import * as colorette from 'colorette';
import { logger, colorize } from '../logger';

describe('Logger in nodejs', () => {
let spyingStderr: jest.SpyInstance;

beforeEach(() => {
spyingStderr = jest.spyOn(process.stderr, 'write').mockImplementation();
});

afterEach(() => {
spyingStderr.mockRestore();
});

it('should call "process.stderr.write" for error severity', () => {
logger.error('error');

expect(spyingStderr).toBeCalledTimes(1);
expect(spyingStderr).toBeCalledWith(colorette.red('error'));
});

it('should call "process.stderr.write" for warn severity', () => {
logger.warn('warn');

expect(spyingStderr).toBeCalledTimes(1);
expect(spyingStderr).toBeCalledWith(colorette.yellow('warn'));
});

it('should call "process.stderr.write" for info severity', () => {
logger.info('info');

expect(spyingStderr).toBeCalledTimes(1);
expect(spyingStderr).toBeCalledWith('info');
});
});

describe('colorize in nodejs', () => {
it('should call original colorette lib', () => {
const color = 'cyan';
const spyingCyan = jest.spyOn(colorette, color);

const colorized = colorize.cyan(color);

expect(spyingCyan).toBeCalledWith(color);
expect(colorized).toEqual(colorette[color](color));
});
});
18 changes: 18 additions & 0 deletions packages/core/src/__tests__/output-browser.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* @jest-environment jsdom
*/

import { output } from '../output';

describe('output', () => {
it('should ignore all parsable data in browser', () => {
const spyingStdout = jest.spyOn(process.stdout, 'write').mockImplementation();
const data = '{ "errors" : [] }';

output.write(data);

expect(spyingStdout).not.toBeCalled();

spyingStdout.mockRestore();
});
});
15 changes: 15 additions & 0 deletions packages/core/src/__tests__/output.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { output } from '../output';

describe('output', () => {
it('should write all parsable data to stdout', () => {
const spyingStdout = jest.spyOn(process.stdout, 'write').mockImplementation();
const data = '{ "errors" : [] }';

output.write(data);

expect(spyingStdout).toBeCalledTimes(1);
expect(spyingStdout).toBeCalledWith(data);

spyingStdout.mockRestore();
});
});
11 changes: 11 additions & 0 deletions packages/core/src/__tests__/utils-browser.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* @jest-environment jsdom
*/

import { isBrowser } from '../env';

describe('isBrowser', () => {
it('should be browser', () => {
expect(isBrowser).toBe(true);
});
});
7 changes: 7 additions & 0 deletions packages/core/src/__tests__/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
getMatchingStatusCodeRange,
doesYamlFileExist,
} from '../utils';
import { isBrowser } from '../env';
import * as fs from 'fs';
import * as path from 'path';

Expand Down Expand Up @@ -122,5 +123,11 @@ describe('utils', () => {
expect(doesYamlFileExist('redocly.yam')).toBe(false);
});
});

describe('isBrowser', () => {
it('should not be browser', () => {
expect(isBrowser).toBe(false);
});
});
});
});
57 changes: 41 additions & 16 deletions packages/core/src/config/config-resolvers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import * as path from 'path';
import { blue, red } from 'colorette';
import { isAbsoluteUrl } from '../ref-utils';
import { BaseResolver } from '../resolve';
import { defaultPlugin } from './builtIn';
Expand All @@ -21,8 +20,10 @@ import type {
RuleConfig,
DeprecatedInRawConfig,
} from './types';
import { isBrowser } from '../env';
import { isNotString, isString, notUndefined, parseYaml } from '../utils';
import { Config } from './config';
import { colorize, logger } from '../logger';

export async function resolveConfig(rawConfig: RawConfig, configPath?: string): Promise<Config> {
if (rawConfig.styleguide?.extends?.some(isNotString)) {
Expand Down Expand Up @@ -71,35 +72,57 @@ export function resolvePlugins(
): Plugin[] {
if (!plugins) return [];

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const requireFunc = typeof __webpack_require__ === 'function' ? __non_webpack_require__ : require;
// TODO: implement or reuse Resolver approach so it will work in node and browser envs
const requireFunc = (plugin: string | Plugin): Plugin | undefined => {
if (isBrowser && isString(plugin)) {
logger.error(`Cannot load ${plugin}. Plugins aren't supported in browser yet.`);

return undefined;
}

if (isString(plugin)) {
const absoltePluginPath = path.resolve(path.dirname(configPath), plugin);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
return typeof __webpack_require__ === 'function'
? // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
__non_webpack_require__(absoltePluginPath)
: require(absoltePluginPath);
}

return plugin;
};

const seenPluginIds = new Map<string, string>();

return plugins
.map((p) => {
if (isString(p) && isAbsoluteUrl(p)) {
throw new Error(red(`We don't support remote plugins yet.`));
throw new Error(colorize.red(`We don't support remote plugins yet.`));
}

// TODO: resolve npm packages similar to eslint
const pluginModule = isString(p)
? (requireFunc(path.resolve(path.dirname(configPath), p)) as Plugin)
: p;
const pluginModule = requireFunc(p);

if (!pluginModule) {
return;
}

const id = pluginModule.id;
if (typeof id !== 'string') {
throw new Error(red(`Plugin must define \`id\` property in ${blue(p.toString())}.`));
throw new Error(
colorize.red(`Plugin must define \`id\` property in ${colorize.blue(p.toString())}.`)
);
}

if (seenPluginIds.has(id)) {
const pluginPath = seenPluginIds.get(id)!;
throw new Error(
red(
`Plugin "id" must be unique. Plugin ${blue(p.toString())} uses id "${blue(
id
)}" already seen in ${blue(pluginPath)}`
colorize.red(
`Plugin "id" must be unique. Plugin ${colorize.blue(
p.toString()
)} uses id "${colorize.blue(id)}" already seen in ${colorize.blue(pluginPath)}`
)
);
}
Expand Down Expand Up @@ -285,17 +308,19 @@ export function resolvePreset(presetName: string, plugins: Plugin[]): ResolvedSt
const { pluginId, configName } = parsePresetName(presetName);
const plugin = plugins.find((p) => p.id === pluginId);
if (!plugin) {
throw new Error(`Invalid config ${red(presetName)}: plugin ${pluginId} is not included.`);
throw new Error(
`Invalid config ${colorize.red(presetName)}: plugin ${pluginId} is not included.`
);
}

const preset = plugin.configs?.[configName];
if (!preset) {
throw new Error(
pluginId
? `Invalid config ${red(
? `Invalid config ${colorize.red(
presetName
)}: plugin ${pluginId} doesn't export config with name ${configName}.`
: `Invalid config ${red(presetName)}: there is no such built-in config.`
: `Invalid config ${colorize.red(presetName)}: there is no such built-in config.`
);
}
return preset;
Expand Down
4 changes: 1 addition & 3 deletions packages/core/src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { parseYaml, stringifyYaml } from '../js-yaml';
import { slash, doesYamlFileExist } from '../utils';
import { NormalizedProblem } from '../walk';
import { OasVersion, OasMajorVersion, Oas2RuleSet, Oas3RuleSet } from '../oas-types';
import { env } from '../env';

import type { NodeType } from '../types';
import type {
Expand All @@ -19,9 +20,6 @@ import type {
} from './types';
import { getResolveConfig } from './utils';

// Alias environment here so this file can work in browser environments too.
export const env = typeof process !== 'undefined' ? process.env || {} : {};

export const IGNORE_FILE = '.redocly.lint-ignore.yaml';
const IGNORE_BANNER =
`# This file instructs Redocly's linter to ignore the rules contained for specific parts of your API.\n` +
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/config/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { yellow } from 'colorette';
import {
assignExisting,
isTruthy,
Expand All @@ -18,6 +17,7 @@ import type {
ResolvedStyleguideConfig,
RulesFields,
} from './types';
import { logger, colorize } from '../logger';

export function parsePresetName(presetName: string): { pluginId: string; configName: string } {
if (presetName.indexOf('/') > -1) {
Expand Down Expand Up @@ -222,7 +222,7 @@ export function getUniquePlugins(plugins: Plugin[]): Plugin[] {
results.push(p);
seen.add(p.id);
} else if (p.id) {
process.stderr.write(`Duplicate plugin id "${yellow(p.id)}".\n`);
logger.warn(`Duplicate plugin id "${colorize.red(p.id)}".\n`);
}
}
return results;
Expand Down
5 changes: 5 additions & 0 deletions packages/core/src/env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const isBrowser =
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
typeof window !== 'undefined' || typeof self !== 'undefined' || typeof process === 'undefined'; // main and worker thread
export const env = isBrowser ? {} : process.env || {};
20 changes: 13 additions & 7 deletions packages/core/src/format/codeframes.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { gray, red, options as colorOptions } from 'colorette';
import * as yamlAst from 'yaml-ast-parser';
import { unescapePointer } from '../ref-utils';
import { LineColLocationObject, Loc, LocationObject } from '../walk';
import { colorize, colorOptions } from '../logger';

type YAMLMapping = yamlAst.YAMLMapping & { kind: yamlAst.Kind.MAPPING };
type YAMLMap = yamlAst.YamlMap & { kind: yamlAst.Kind.MAP };
Expand Down Expand Up @@ -39,14 +39,20 @@ export function getCodeframe(location: LineColLocationObject, color: boolean) {
const startIdx = i === startLineNum ? start.col - 1 : currentPad;
const endIdx = i === endLineNum ? end.col - 1 : line.length;

prefixedLines.push([`${i}`, markLine(line, startIdx, endIdx, red)]);
prefixedLines.push([`${i}`, markLine(line, startIdx, endIdx, colorize.red)]);
if (!color) prefixedLines.push(['', underlineLine(line, startIdx, endIdx)]);
}

if (skipLines > 0) {
prefixedLines.push([`…`, `${whitespace(currentPad)}${gray(`< ${skipLines} more lines >`)}`]);
prefixedLines.push([
`…`,
`${whitespace(currentPad)}${colorize.gray(`< ${skipLines} more lines >`)}`,
]);
// print last line
prefixedLines.push([`${endLineNum}`, markLine(lines[endLineNum - 1], -1, end.col - 1, red)]);
prefixedLines.push([
`${endLineNum}`,
markLine(lines[endLineNum - 1], -1, end.col - 1, colorize.red),
]);

if (!color) prefixedLines.push(['', underlineLine(lines[endLineNum - 1], -1, end.col - 1)]);
}
Expand All @@ -63,7 +69,7 @@ export function getCodeframe(location: LineColLocationObject, color: boolean) {
line: string,
startIdx: number = -1,
endIdx: number = +Infinity,
variant = gray
variant = colorize.gray
) {
if (!color) return line;
if (!line) return line;
Expand All @@ -90,7 +96,7 @@ function printPrefixedLines(lines: [string, string][]): string {
return existingLines
.map(
([prefix, line]) =>
gray(leftPad(padLen, prefix) + ' |') +
colorize.gray(leftPad(padLen, prefix) + ' |') +
(line ? ' ' + limitLineLength(line.substring(dedentLen)) : '')
)
.join('\n');
Expand All @@ -99,7 +105,7 @@ function printPrefixedLines(lines: [string, string][]): string {
function limitLineLength(line: string, maxLen: number = MAX_LINE_LENGTH) {
const overflowLen = line.length - maxLen;
if (overflowLen > 0) {
const charsMoreText = gray(`...<${overflowLen} chars>`);
const charsMoreText = colorize.gray(`...<${overflowLen} chars>`);
return line.substring(0, maxLen - charsMoreText.length) + charsMoreText;
} else {
return line;
Expand Down
Loading

0 comments on commit c53c50d

Please sign in to comment.