chore(client): make curriculum helpers external (#46906)

* chore: make curriculum helpers external

* feat: add curriculum helpers as a package

* fix: use right preset?

* fix: update version with babel transpiled files

* fix: re-add fixture

* fix: declare as module

* fix: add declaration files in curriculum helpers

* fix: nothing with version 1.0.4

* fix: missing functions in package

* fix: remove old babel

* fix: linting
This commit is contained in:
Sem Bauke
2022-07-27 19:00:37 +02:00
committed by GitHub
parent 9c0d3fe1d6
commit 2440d3f43f
12 changed files with 31 additions and 865 deletions
+1
View File
@@ -43,6 +43,7 @@
"@fortawesome/free-brands-svg-icons": "6.1.1",
"@fortawesome/free-solid-svg-icons": "6.1.1",
"@fortawesome/react-fontawesome": "0.2.0",
"@freecodecamp/curriculum-helpers": "1.0.5",
"@freecodecamp/loop-protect": "2.2.1",
"@freecodecamp/react-bootstrap": "0.32.3",
"@freecodecamp/react-calendar-heatmap": "1.0.0",
+2 -2
View File
@@ -1,6 +1,6 @@
import '@babel/polyfill';
import jQuery from 'jquery';
import curriculumHelpers from '../utils/curriculum-helpers';
import * as helpers from '@freecodecamp/curriculum-helpers';
declare global {
interface Window {
@@ -86,7 +86,7 @@ async function initTestFrame(e: InitTestFrameArg = { code: {} }) {
// eslint-disable-next-line no-inline-comments
const { default: chai } = await import(/* webpackChunkName: "chai" */ 'chai');
const assert = chai.assert;
const __helpers = curriculumHelpers;
const __helpers = helpers;
/* eslint-enable @typescript-eslint/no-unused-vars */
let Enzyme;
+7 -7
View File
@@ -1,9 +1,7 @@
import chai from 'chai';
import '@babel/polyfill';
import { toString as __toString } from 'lodash-es';
import curriculumHelpers, {
removeJSComments
} from '../../utils/curriculum-helpers';
import * as helpers from '@freecodecamp/curriculum-helpers';
import { format as __format } from '../../utils/format';
const ctx: Worker & typeof globalThis = self as unknown as Worker &
@@ -80,14 +78,14 @@ interface TestEvaluatorEvent extends MessageEvent {
ctx.onmessage = async (e: TestEvaluatorEvent) => {
/* eslint-disable @typescript-eslint/no-unused-vars */
let code = (e.data?.code?.contents || '').slice();
code = e.data?.removeComments ? removeJSComments(code) : code;
code = e.data?.removeComments ? helpers.removeJSComments(code) : code;
let editableContents = (e.data?.code?.editableContents || '').slice();
editableContents = e.data?.removeComments
? removeJSComments(editableContents)
? helpers.removeJSComments(editableContents)
: editableContents;
const assert = chai.assert;
const __helpers = curriculumHelpers;
const __helpers = helpers;
// Fake Deep Equal dependency
const DeepEqual = (a: unknown, b: unknown) =>
JSON.stringify(a) === JSON.stringify(b);
@@ -105,7 +103,9 @@ ctx.onmessage = async (e: TestEvaluatorEvent) => {
// Logging is proxyed after the build to catch console.log messages
// generated during testing.
testResult = (await eval(`${
e.data?.removeComments ? removeJSComments(e.data.build) : e.data.build
e.data?.removeComments
? helpers.removeJSComments(e.data.build)
: e.data.build
}
__utils.flushLogs();
__userCodeWasExecuted = true;
@@ -1,352 +0,0 @@
const cssFullExample = `
a {
color: green;
display: flex;
}
.aClass {
font-size: 32px;
/* the property below should not appear in final css string
width: 400px;
height: 200px;
*/
flex: 1;
flex-direction: row;
}
/* Set the background color to blue for screens that are 300px or less */
@media screen and (max-width: 300px) {
body {
background-color: blue;
}
}`;
const cssCodeWithCommentsRemoved = `
a {
color: green;
display: flex;
}
.aClass {
font-size: 32px;
flex: 1;
flex-direction: row;
}
@media screen and (max-width: 300px) {
body {
background-color: blue;
}
}`;
export const cssString = `:root {
--building-color1: #aa80ff;
--building-color2: #66cc99;
--building-color3: #cc6699;
--building-color4: #538cc6;
--window-color1: #bb99ff;
--window-color2: #8cd9b3;
--window-color3: #d98cb3;
--window-color4: #8cb3d9;
}
* {
box-sizing: border-box;
}
body {
height: 100vh;
margin: 0;
overflow: hidden;
}
.background-buildings,
.foreground-buildings {
width: 100%;
height: 100%;
display: flex;
align-items: flex-end;
justify-content: space-evenly;
position: absolute;
top: 0;
}
.building-wrap {
display: flex;
flex-direction: column;
align-items: center;
}
.window-wrap {
display: flex;
align-items: center;
justify-content: space-evenly;
}
.sky {
background: radial-gradient(
closest-corner circle at 15% 15%,
#ffcf33,
#ffcf33 20%,
#ffff66 21%,
#bbeeff 100%
);
}
/* BACKGROUND BUILDINGS - "bb" stands for "background building" */
.bb1 {
width: 10%;
height: 70%;
}
.bb1a {
width: 70%;
}
.bb1b {
width: 80%;
}
.bb1c {
width: 90%;
}
.bb1d {
width: 100%;
height: 70%;
background: linear-gradient(var(--building-color1) 50%, var(--window-color1));
}
.bb1-window {
height: 10%;
background: linear-gradient(var(--building-color1), var(--window-color1));
}
.bb2 {
width: 10%;
height: 50%;
}
.bb2a {
border-bottom: 5vh solid var(--building-color2);
border-left: 5vw solid transparent;
border-right: 5vw solid transparent;
}
.bb2b {
width: 100%;
height: 100%;
background: repeating-linear-gradient(
var(--building-color2),
var(--building-color2) 6%,
var(--window-color2) 6%,
var(--window-color2) 9%
);
}
.bb3 {
width: 10%;
height: 55%;
background: repeating-linear-gradient(
90deg,
var(--building-color3),
var(--building-color3),
var(--window-color3) 15%
);
}
.bb4 {
width: 11%;
height: 58%;
}
.bb4a {
width: 3%;
height: 10%;
background-color: var(--building-color4);
}
.bb4b {
width: 80%;
height: 5%;
background-color: var(--building-color4);
}
.bb4c {
width: 100%;
height: 85%;
background-color: var(--building-color4);
}
.bb4-window {
width: 18%;
height: 90%;
background-color: var(--window-color4);
}
/* FOREGROUND BUILDINGS - "fb" stands for "foreground building" */
.fb1 {
width: 10%;
height: 60%;
}
.fb1a {
border-bottom: 7vh solid var(--building-color4);
border-left: 2vw solid transparent;
border-right: 2vw solid transparent;
}
.fb1b {
width: 60%;
height: 10%;
background-color: var(--building-color4);
}
.fb1c {
width: 100%;
height: 80%;
background: repeating-linear-gradient(
90deg,
var(--building-color4),
var(--building-color4) 10%,
transparent 10%,
transparent 15%
),
repeating-linear-gradient(
var(--building-color4),
var(--building-color4) 10%,
var(--window-color4) 10%,
var(--window-color4) 90%
);
}
.fb2 {
width: 10%;
height: 40%;
}
.fb2a {
width: 100%;
border-bottom: 10vh solid var(--building-color3);
border-left: 1vw solid transparent;
border-right: 1vw solid transparent;
}
.fb2b {
width: 100%;
height: 75%;
background-color: var(--building-color3);
}
.fb2-window {
width: 22%;
height: 100%;
background-color: var(--window-color3);
}
.fb3 {
width: 10%;
height: 35%;
}
.fb3a {
width: 80%;
height: 15%;
background-color: var(--building-color1);
}
.fb3b {
width: 100%;
height: 35%;
background-color: var(--building-color1);
}
.fb3-window {
width: 25%;
height: 80%;
background-color: var(--window-color1);
}
.fb4 {
width: 8%;
height: 45%;
position: relative;
left: 10%;
}
.fb4a {
border-top: 5vh solid transparent;
border-left: 8vw solid var(--building-color1);
}
.fb4b {
width: 100%;
height: 89%;
background-color: var(--building-color1);
display: flex;
flex-wrap: wrap;
}
.fb4-window {
width: 30%;
height: 10%;
border-radius: 50%;
background-color: var(--window-color1);
margin: 10%;
}
.fb5 {
width: 10%;
height: 33%;
position: relative;
right: 10%;
background: repeating-linear-gradient(
var(--building-color2),
var(--building-color2) 5%,
transparent 5%,
transparent 10%
),
repeating-linear-gradient(
90deg,
var(--building-color2),
var(--building-color2) 12%,
var(--window-color2) 12%,
var(--window-color2) 44%
);
}
.fb6 {
width: 9%;
height: 38%;
background: repeating-linear-gradient(
90deg,
var(--building-color3),
var(--building-color3) 10%,
transparent 10%,
transparent 30%
),
repeating-linear-gradient(
var(--building-color3),
var(--building-color3) 10%,
var(--window-color3) 10%,
var(--window-color3) 30%
);
}
@media (max-width: 1000px) {
.sky {
background: radial-gradient(
closest-corner circle at 15% 15%,
#ffcf33,
#ffcf33 20%,
#ffff66 21%,
#bbeeff 100%
);
}
}
`;
const testValues = {
cssFullExample,
cssCodeWithCommentsRemoved
};
export default testValues;
@@ -1,30 +0,0 @@
const htmlFullExample = `
<!--
multi line html comment
-->
not a comment
not a commment <!-- single line html comment --> not a comment
not a comment
<!-- this is my blog: <mynixworld.inf> -->
not a comment
`;
const htmlCodeWithCommentsRemoved = `
not a comment
not a commment not a comment
not a comment
not a comment
`;
const testValues = {
htmlFullExample,
htmlCodeWithCommentsRemoved
};
export default testValues;
@@ -1,70 +0,0 @@
const jsCodeWithSingleAndMultLineComments = `
function nonMutatingPush(original, newItem) {
/* This is a
multi-line comment
that should be removed. */
return original.push(newItem);
}
var first = [1, 2, 3];
// This is a single line comment
var second = [4, 5];
nonMutatingPush(first, second);`;
const jsCodeWithSingleAndMultLineCommentsRemoved = `
function nonMutatingPush(original, newItem) {
return original.push(newItem);
}
var first = [1, 2, 3];
var second = [4, 5];
nonMutatingPush(first, second);`;
const jsCodeWithUrl = `
function nonMutatingPush(original, newItem) {
var url = 'https://freecodecamp.org'; // this comment should vanish
return original.push(newItem);
}`;
const jsCodeWithUrlUnchanged = `
function nonMutatingPush(original, newItem) {
var url = 'https://freecodecamp.org';
return original.push(newItem);
}`;
const jsCodeWithNoCall = `function myFunc() {
return Math.random();
}
`;
const jsCodeWithNoArgCall = `function myFunc() {
return Math.random();
}
myFunc();
`;
const jsCodeWithArgCall = `function myFunc() {
return Math.random();
}
myFunc('this shouldn't be here');
`;
const jsCodeWithCommentedCall = `function myFunc() {
return Math.random();
}
/*
myFunc();
*/
`;
const testValues = {
jsCodeWithSingleAndMultLineComments,
jsCodeWithSingleAndMultLineCommentsRemoved,
jsCodeWithUrl,
jsCodeWithUrlUnchanged,
jsCodeWithNoCall,
jsCodeWithNoArgCall,
jsCodeWithArgCall,
jsCodeWithCommentedCall
};
export default testValues;
@@ -1,14 +0,0 @@
const stringWithWhiteSpaceChars = `
This string sentence has various white spaces characters:
\t* This line starts with a tab character.
\t* This line has several preceding white space characters.`;
const stringWithWhiteSpaceCharsRemoved =
'Thisstringsentencehasvariouswhitespacescharacters:*Thislinestartswithatabcharacter.*Thislinehasseveralprecedingwhitespacecharacters.';
const testValues = {
stringWithWhiteSpaceChars,
stringWithWhiteSpaceCharsRemoved
};
export default testValues;
-96
View File
@@ -1,96 +0,0 @@
import { cssString } from './__fixtures/curriculum-helpers-css';
import CSSHelp from './css-help';
describe('css-help', () => {
const doc = document;
let t: CSSHelp;
beforeEach(() => {
const style = doc.createElement('style');
style.innerHTML = cssString as string;
doc.head.appendChild(style);
t = new CSSHelp(doc);
// JSDOM does not implement latest CSSOM spec. As such,
// conditionText property needs to be manually added.
// REF: https://github.com/freeCodeCamp/freeCodeCamp/pull/42148#issuecomment-847291137
const mediaRule = t.getCSSRules('media')?.[0] as CSSMediaRule;
const conditionText = mediaRule.media[0];
mediaRule.conditionText = conditionText;
});
describe('getStyle', () => {
it('should return an ExtendedCSSStyleDeclartion object of length 1', () => {
expect(t.getStyle('*')?.length).toEqual(1);
});
it('should return a non-empty ExtendedCSSStyleDeclaration object', () => {
expect(t.getStyle('.bb1')).toBeTruthy();
});
it('should return a whitespaceless string', () => {
expect(t.getStyle('.bb1d')?.getPropVal('background', true)).toEqual(
'linear-gradient(var(--building-color1)50%,var(--window-color1))'
);
});
});
describe('isPropertyUsed', () => {
it('should return true on existing properties', () => {
expect(t.isPropertyUsed('height')).toBeTruthy();
});
it('should return true on existing custom properties', () => {
expect(t.isPropertyUsed('--building-color1')).toBeTruthy();
});
});
describe('isDeclaredAfter', () => {
it('should return true if existing style is declared after another', () => {
expect(t.getStyleRule('.bb1a')?.isDeclaredAfter('.bb1')).toBeTruthy();
});
});
describe('getPropertyValue', () => {
it('should return custom property value needing trim', () => {
expect(
t.getStyle(':root')?.getPropertyValue('--building-color1')?.trim()
).toEqual('#aa80ff');
});
it('should return value to existing property', () => {
expect(
t.getStyle('.bb4a')?.getPropertyValue('background-color')
).toBeTruthy();
});
it('should return property value without evaluating result', () => {
expect(t.getStyle('.bb4a')?.getPropertyValue('background-color')).toEqual(
'var(--building-color4)'
);
});
});
describe('getCSSRules', () => {
it('should return a CSSRules array of length 1', () => {
expect(t.getCSSRules('media')?.length).toEqual(1);
});
});
describe('getRuleListsWithinMedia', () => {
it('should return a CSSMediaRule array with a selectable CSSStyleRule', () => {
expect(
t
.getRuleListsWithinMedia('(max-width: 1000px)')
.find(x => x.selectorText === '.sky')
).toBeTruthy();
});
it('should return CSSStyleDeclaration property with complex value', () => {
// NOTE: JSDOM causes value to have tabbed characters, DOM has single-line values.
expect(
t
.getRuleListsWithinMedia('(max-width: 1000px)')
.find(x => x.selectorText === '.sky')?.style?.background
).toEqual(
`radial-gradient(
closest-corner circle at 15% 15%,
#ffcf33,
#ffcf33 20%,
#ffff66 21%,
#bbeeff 100%
)`
);
});
});
afterEach(() => {
document.body.innerHTML = '';
document.head.innerHTML = '';
});
});
-128
View File
@@ -1,128 +0,0 @@
export interface ExtendedStyleRule extends CSSStyleRule {
isDeclaredAfter: (selector: string) => boolean;
}
interface ExtendedStyleDeclaration extends CSSStyleDeclaration {
getPropVal: (prop: string, strip?: boolean) => string;
}
const getIsDeclaredAfter = (styleRule: CSSStyleRule) => (selector: string) => {
const cssStyleRules = Array.from(
styleRule.parentStyleSheet?.cssRules || []
)?.filter(ele => ele.type === CSSRule.STYLE_RULE) as CSSStyleRule[];
const previousStyleRule = cssStyleRules.find(
ele => ele?.selectorText === selector
);
if (!previousStyleRule) return false;
const currPosition = Array.from(
styleRule.parentStyleSheet?.cssRules || []
).indexOf(styleRule);
const prevPosition = Array.from(
previousStyleRule?.parentStyleSheet?.cssRules || []
).indexOf(previousStyleRule);
return currPosition > prevPosition;
};
class CSSHelp {
doc: HTMLDocument;
constructor(doc: HTMLDocument) {
this.doc = doc;
}
private _getStyleRules() {
const styleSheet = this.getStyleSheet();
return this.styleSheetToCssRulesArray(styleSheet).filter(
ele => ele.type === CSSRule.STYLE_RULE
) as CSSStyleRule[];
}
getStyleDeclarations(selector: string): CSSStyleDeclaration[] {
return this._getStyleRules()
?.filter(ele => ele?.selectorText === selector)
.map(x => x.style);
}
getStyle(selector: string): ExtendedStyleDeclaration | null {
const style = this._getStyleRules().find(
ele => ele?.selectorText === selector
)?.style as ExtendedStyleDeclaration | undefined;
if (!style) return null;
style.getPropVal = (prop: string, strip = false) => {
return strip
? style.getPropertyValue(prop).replace(/\s+/g, '')
: style.getPropertyValue(prop);
};
return style;
}
getStyleRule(selector: string): ExtendedStyleRule | null {
const styleRule = this._getStyleRules()?.find(
ele => ele?.selectorText === selector
);
if (styleRule) {
return {
...styleRule,
isDeclaredAfter: (selector: string) =>
getIsDeclaredAfter(styleRule)(selector)
};
} else {
return null;
}
}
getCSSRules(element?: string): CSSRule[] {
const styleSheet = this.getStyleSheet();
const cssRules = this.styleSheetToCssRulesArray(styleSheet);
switch (element) {
case 'media':
return cssRules.filter(ele => ele.type === CSSRule.MEDIA_RULE);
case 'fontface':
return cssRules.filter(ele => ele.type === CSSRule.FONT_FACE_RULE);
case 'import':
return cssRules.filter(ele => ele.type === CSSRule.IMPORT_RULE);
case 'keyframes':
return cssRules.filter(ele => ele.type === CSSRule.KEYFRAMES_RULE);
default:
return cssRules;
}
}
isPropertyUsed(property: string): boolean {
return this._getStyleRules().some(ele =>
ele.style?.getPropertyValue(property)
);
}
getRuleListsWithinMedia(mediaText: string): CSSStyleRule[] {
const medias = this.getCSSRules('media') as CSSMediaRule[];
const cond = medias?.find(x => x?.media?.mediaText === mediaText);
const cssRules = cond?.cssRules;
return Array.from(cssRules || []) as CSSStyleRule[];
}
getStyleSheet(): CSSStyleSheet | null {
// TODO: Change selector to match exactly 'styles.css'
const link: HTMLLinkElement | null = this.doc?.querySelector(
"link[href*='styles']"
);
// When using the styles.css tab, we add a 'fcc-injected-styles' class so we can target that. This allows users to add external scripts without them interfering
const stylesDotCss: HTMLStyleElement | null = this.doc?.querySelector(
'style.fcc-injected-styles'
);
// For steps that use <style> tags, where they don't add the above class - most* browser extensions inject styles with class/media attributes, so it filters those
const styleTag: HTMLStyleElement | null = this.doc?.querySelector(
'style:not([class]):not([media])'
);
if (link?.sheet?.cssRules?.length) {
return link.sheet;
} else if (stylesDotCss) {
return stylesDotCss.sheet;
} else if (styleTag) {
return styleTag.sheet;
} else {
return null;
}
}
styleSheetToCssRulesArray(
styleSheet: ReturnType<CSSHelp['getStyleSheet']>
): CSSRule[] {
return Array.from(styleSheet?.cssRules || []);
}
}
export default CSSHelp;
-108
View File
@@ -1,108 +0,0 @@
import cssTestValues from './__fixtures/curriculum-helpers-css';
import htmlTestValues from './__fixtures/curriculum-helpers-html';
import jsTestValues from './__fixtures/curriculum-helpers-javascript';
import whiteSpaceTestValues from './__fixtures/curriculum-helpers-remove-white-space';
import __testHelpers, { removeJSComments } from './curriculum-helpers';
const { stringWithWhiteSpaceChars, stringWithWhiteSpaceCharsRemoved } =
whiteSpaceTestValues;
const { cssFullExample, cssCodeWithCommentsRemoved } = cssTestValues;
const { htmlFullExample, htmlCodeWithCommentsRemoved } = htmlTestValues;
const {
jsCodeWithSingleAndMultLineComments,
jsCodeWithSingleAndMultLineCommentsRemoved,
jsCodeWithUrl,
jsCodeWithUrlUnchanged,
jsCodeWithNoCall,
jsCodeWithNoArgCall,
jsCodeWithArgCall,
jsCodeWithCommentedCall
} = jsTestValues;
describe('removeWhiteSpace', () => {
const { removeWhiteSpace } = __testHelpers;
it('returns a string', () => {
expect(typeof removeWhiteSpace('This should return a string')).toBe(
'string'
);
});
it('returns a string with no white space characters', () => {
expect(removeWhiteSpace(stringWithWhiteSpaceChars)).toBe(
stringWithWhiteSpaceCharsRemoved
);
});
});
describe('removeJSComments', () => {
it('returns a string', () => {
expect(typeof removeJSComments('const should = "return a string"')).toBe(
'string'
);
});
it('returns a string with no single or multi-line comments', () => {
expect(removeJSComments(jsCodeWithSingleAndMultLineComments)).toBe(
jsCodeWithSingleAndMultLineCommentsRemoved
);
});
it('leaves malformed JS unchanged', () => {
const actual = '/ unclosed regex';
expect(removeJSComments(actual)).toBe(actual);
});
it('does not remove a url found in JS code', () => {
expect(removeJSComments(jsCodeWithUrl)).toBe(jsCodeWithUrlUnchanged);
});
});
describe('removeCssComments', () => {
const { removeCssComments } = __testHelpers;
it('returns a string', () => {
expect(typeof removeCssComments('.aClass: { color: red; }')).toBe('string');
});
it('returns a CSS string with no single or multi-line comments', () => {
expect(removeCssComments(cssFullExample)).toBe(cssCodeWithCommentsRemoved);
});
});
describe('removeHtmlComments', () => {
const { removeHtmlComments } = __testHelpers;
it('returns a string', () => {
expect(
typeof removeHtmlComments(
'<h1>hello world</h1><!-- a comment--><h2>h2 element</h2>'
)
).toBe('string');
});
it('returns an HTML string with no single or multi-line comments', () => {
expect(removeHtmlComments(htmlFullExample)).toBe(
htmlCodeWithCommentsRemoved
);
});
});
describe('isCalledWithNoArgs', () => {
const { isCalledWithNoArgs } = __testHelpers;
it('returns a boolean', () => {
expect(typeof isCalledWithNoArgs('foo', 'bar')).toBe('boolean');
});
it('returns false when not called', () => {
expect(isCalledWithNoArgs('myFunc', jsCodeWithNoCall)).toBe(false);
});
it('returns true for a call with no arguments', () => {
expect(isCalledWithNoArgs('myFunc', jsCodeWithNoArgCall)).toBe(true);
});
it('returns false for a call with arguments', () => {
expect(isCalledWithNoArgs('myFunc', jsCodeWithArgCall)).toBe(false);
});
it('returns false for a commented out call', () => {
expect(isCalledWithNoArgs('myFunc', jsCodeWithCommentedCall)).toBe(false);
});
});
-58
View File
@@ -1,58 +0,0 @@
import strip from '@freecodecamp/strip-comments';
import CSSHelp from './css-help';
const removeHtmlComments = (str: string): string =>
str.replace(/<!--[\s\S]*?(-->|$)/g, '');
const removeCssComments = (str: string): string =>
str.replace(/\/\*[\s\S]+?\*\//g, '');
export const removeJSComments = (codeStr: string): string => {
// TODO: publish type declarations and re-enable eslint
try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
return strip(codeStr) as string;
} catch (err) {
return codeStr;
}
};
const removeWhiteSpace = (str = ''): string => {
return str.replace(/\s/g, '');
};
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping
function escapeRegExp(exp: string): string {
return exp.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
/*
This helper checks if a function/method is called with no arguments.
Because Safari does not support lookbehinds (as of writing this on
July 14 2021), avoiding false matches on function definitions is done by
checking that only whitespace characters precede the calling name on the line
it is found on. That makes this helper incompatible with
removeWhiteSpace() above, which removes all whitespace characters.
*/
function isCalledWithNoArgs(
calledFuncName: string,
callingCode: string
): boolean {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
const noCommentsCallingCode = strip(callingCode) as string;
const funcExp = `^\\s*?${escapeRegExp(calledFuncName)}\\(\\s*?\\)`;
const matches = new RegExp(funcExp, 'gm').exec(noCommentsCallingCode) ?? [];
return !!matches.length;
}
const curriculumHelpers = {
removeHtmlComments,
removeCssComments,
removeWhiteSpace,
isCalledWithNoArgs,
CSSHelp
};
export default curriculumHelpers;
+21
View File
@@ -446,6 +446,7 @@
"@fortawesome/free-brands-svg-icons": "6.1.1",
"@fortawesome/free-solid-svg-icons": "6.1.1",
"@fortawesome/react-fontawesome": "0.2.0",
"@freecodecamp/curriculum-helpers": "1.0.5",
"@freecodecamp/loop-protect": "2.2.1",
"@freecodecamp/react-bootstrap": "0.32.3",
"@freecodecamp/react-calendar-heatmap": "1.0.0",
@@ -3372,6 +3373,17 @@
"resolved": "tools/challenge-helper-scripts",
"link": true
},
"node_modules/@freecodecamp/curriculum-helpers": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@freecodecamp/curriculum-helpers/-/curriculum-helpers-1.0.5.tgz",
"integrity": "sha512-GjaJXju1slWKtNb01xY6lbDjhuIQLEcLysdhi8d1eIfnvx94Jaxh1tBvxYiFbeI1xf+oeS+6gAy/GIEa7PP0KQ==",
"dependencies": {
"@freecodecamp/strip-comments": "^3.0.1"
},
"engines": {
"npm": ">= 4.0.0"
}
},
"node_modules/@freecodecamp/fcc-source-challenges": {
"resolved": "client/plugins/fcc-source-challenges",
"link": true
@@ -56312,6 +56324,7 @@
"@fortawesome/free-brands-svg-icons": "6.1.1",
"@fortawesome/free-solid-svg-icons": "6.1.1",
"@fortawesome/react-fontawesome": "0.2.0",
"@freecodecamp/curriculum-helpers": "1.0.5",
"@freecodecamp/loop-protect": "2.2.1",
"@freecodecamp/react-bootstrap": "0.32.3",
"@freecodecamp/react-calendar-heatmap": "1.0.0",
@@ -56543,6 +56556,14 @@
"typescript": "4.7.4"
}
},
"@freecodecamp/curriculum-helpers": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@freecodecamp/curriculum-helpers/-/curriculum-helpers-1.0.5.tgz",
"integrity": "sha512-GjaJXju1slWKtNb01xY6lbDjhuIQLEcLysdhi8d1eIfnvx94Jaxh1tBvxYiFbeI1xf+oeS+6gAy/GIEa7PP0KQ==",
"requires": {
"@freecodecamp/strip-comments": "^3.0.1"
}
},
"@freecodecamp/fcc-source-challenges": {
"version": "file:client/plugins/fcc-source-challenges"
},