Skip to content

Commit

Permalink
feat: lint YAML files (#699)
Browse files Browse the repository at this point in the history
* lint YAML files

* use page-constructor & leading page schema

* remove comments

* add tests & del unused fn
  • Loading branch information
martyanovandrey authored Mar 26, 2024
1 parent d42d43c commit 5b28f97
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 1 deletion.
37 changes: 36 additions & 1 deletion src/resolvers/lintPage.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,26 @@
import {dirname, relative, resolve} from 'path';
import {load} from 'js-yaml';
import log from '@diplodoc/transform/lib/log';
import {
LintMarkdownFunctionOptions,
PluginOptions,
default as yfmlint,
} from '@diplodoc/transform/lib/yfmlint';
import {isLocalUrl} from '@diplodoc/transform/lib/utils';
import {getLogLevel} from '@diplodoc/transform/lib/yfmlint/utils';
import {LINK_KEYS} from '@diplodoc/client/ssr';

import {readFileSync} from 'fs';
import {bold} from 'chalk';

import {ArgvService, PluginService} from '../services';
import {getVarsPerFile, getVarsPerRelativeFile} from '../utils';
import {
checkPathExists,
findAllValuesByKeys,
getLinksWithExtension,
getVarsPerFile,
getVarsPerRelativeFile,
} from '../utils';
import {liquidMd2Html} from './md2html';
import {liquidMd2Md} from './md2md';

Expand All @@ -20,6 +31,7 @@ interface FileTransformOptions {

const FileLinter: Record<string, Function> = {
'.md': MdFileLinter,
'.yaml': YamlFileLinter,
};

export interface ResolverLintOptions {
Expand Down Expand Up @@ -53,6 +65,29 @@ export function lintPage(options: ResolverLintOptions) {
}
}

function YamlFileLinter(content: string, lintOptions: FileTransformOptions): void {
const {input, lintConfig} = ArgvService.getConfig();
const {path: filePath} = lintOptions;
const currentFilePath: string = resolve(input, filePath);

const logLevel = getLogLevel({
logLevelsConfig: lintConfig['log-levels'],
ruleNames: ['YAML001'],
defaultLevel: log.LogLevels.ERROR,
});

const contentLinks = findAllValuesByKeys(load(content), LINK_KEYS);
const localLinks = contentLinks.filter(
(link) => getLinksWithExtension(link) && isLocalUrl(link),
);

return localLinks.forEach(
(link) =>
checkPathExists(link, currentFilePath) ||
log[logLevel](`Link is unreachable: ${bold(link)} in ${bold(currentFilePath)}`),
);
}

function MdFileLinter(content: string, lintOptions: FileTransformOptions): void {
const {input, lintConfig, disableLiquid, outputFormat, ...options} = ArgvService.getConfig();
const {path: filePath} = lintOptions;
Expand Down
33 changes: 33 additions & 0 deletions src/utils/markup.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {join} from 'path';
import {platform} from 'process';
import {flatMapDeep, isArray, isObject, isString} from 'lodash';

import {CUSTOM_STYLE, Platforms, RTL_LANGS} from '../constants';
import {LeadingPage, Resources, SinglePageResult, TextItems, VarsMetadata} from '../models';
Expand All @@ -8,6 +9,7 @@ import {preprocessPageHtmlForSinglePage} from './singlePage';

import {DocInnerProps, DocPageData, render} from '@diplodoc/client/ssr';
import manifest from '@diplodoc/client/manifest';
import {isFileExists, resolveRelativePath} from '@diplodoc/transform/lib/utilsFS';

import {escape} from 'html-escaper';

Expand Down Expand Up @@ -168,3 +170,34 @@ export function joinSinglePageResults(
export function replaceDoubleToSingleQuotes(str: string): string {
return str.replace(/"/g, "'");
}

export function findAllValuesByKeys(obj, keysToFind) {
return flatMapDeep(obj, (value, key) => {
if (
keysToFind.includes(key) &&
(isString(value) || (isArray(value) && value.every(isString)))
) {
return [value];
}

if (isObject(value)) {
return findAllValuesByKeys(value, keysToFind);
}

return [];
});
}

export function getLinksWithExtension(link) {
const oneLineWithExtension = new RegExp(
/^\S.*\.(md|html|yaml|svg|png|gif|jpg|jpeg|bmp|webp|ico)$/gm,
);

return oneLineWithExtension.test(link);
}

export function checkPathExists(path, parentFilePath) {
const includePath = resolveRelativePath(parentFilePath, path);

return isFileExists(includePath);
}
35 changes: 35 additions & 0 deletions tests/units/services/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {filterTextItems, filterFiles, firstFilterTextItems, liquidField} from "s
import {Lang} from "../../../src/constants";
import {ArgvService} from "../../../src/services";
import {YfmArgv} from "models";
import {findAllValuesByKeys} from "utils";

const combinedVars = {
lang: Lang.EN,
Expand Down Expand Up @@ -240,3 +241,37 @@ describe('liquidField', () => {
expect(result).toBe(`{% if type == 'a' %}{{a}}{% else %}{{b}}{% endif %}`);
});
});

describe('findAllValuesByKeys', () => {
beforeEach(() => {
jest.resetAllMocks();
});

test('should return an empty array if no values match the keys', () => {
const obj = {a: 1, b: 2};
const keysToFind = ['c', 'd'];
const result = findAllValuesByKeys(obj, keysToFind);
expect(result).toEqual([]);
});

test('should return an array of string values that match the keys', () => {
const obj = {a: 'foo', b: 'bar', c: 'baz'};
const keysToFind = ['a', 'b'];
const result = findAllValuesByKeys(obj, keysToFind);
expect(result).toEqual(['foo', 'bar']);
});

test('should return an array of string values from nested objects that match the keys', () => {
const obj = {a: {b: 'foo'}, c: [{d: 'bar'}, {e: 'baz'}]};
const keysToFind = ['b', 'd', 'e'];
const result = findAllValuesByKeys(obj, keysToFind);
expect(result).toEqual(['foo', 'bar', 'baz']);
});

test('should return an array of string values from arrays that match the keys', () => {
const obj = {a: ['foo', 'bar'], b: [1, 2]};
const keysToFind = ['a'];
const result = findAllValuesByKeys(obj, keysToFind);
expect(result).toEqual(['foo', 'bar']);
});
});

0 comments on commit 5b28f97

Please sign in to comment.