ci: Update JS tooling
Provided we use the correct version of the SpiderMonkey shell, we can perform checks using the same engine that is used by gjs. However some engine features are opt-in, so the set of features enabled by gjs and js102 may differ. The obvious option for avoiding this is replacing js102 with gjs for tests. For that reason the check-potfiles.js script has been ported to gjs and a second (simpler) script to replace `js102 --compileonly` was added. This work happened in a separate repository to make sharing between different JS-based projects easier. Update the CI image to pull in those scripts and include gjs. This will also address the issue that `js102 --compileonly` does not handle modules. Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/2193>
This commit is contained in:
parent
ddae41456d
commit
0a420404c6
@ -28,7 +28,6 @@ default:
|
||||
variables:
|
||||
FDO_UPSTREAM_REPO: GNOME/gnome-shell
|
||||
BUNDLE: "extensions-git.flatpak"
|
||||
JS_LOG: "js-report.txt"
|
||||
LINT_LOG: "eslint-report.xml"
|
||||
LINT_MR_LOG: "eslint-mr-report.xml"
|
||||
|
||||
@ -49,13 +48,13 @@ workflow:
|
||||
.gnome-shell.fedora:
|
||||
variables:
|
||||
FDO_DISTRIBUTION_VERSION: 38
|
||||
FDO_DISTRIBUTION_TAG: '2023-04-20.0'
|
||||
FDO_DISTRIBUTION_TAG: '2023-04-21.1'
|
||||
FDO_DISTRIBUTION_PACKAGES:
|
||||
findutils
|
||||
mozjs102-devel
|
||||
gjs
|
||||
nodejs
|
||||
npm
|
||||
meson
|
||||
pkgconfig(gobject-introspection-1.0)
|
||||
pkgconfig(gio-2.0)
|
||||
pkgconfig(gio-unix-2.0)
|
||||
pkgconfig(gnome-autoar-0)
|
||||
@ -67,6 +66,10 @@ workflow:
|
||||
dnf group install -y 'Development Tools' \
|
||||
'C Development Tools and Libraries' &&
|
||||
|
||||
./.gitlab-ci/install-meson-project.sh \
|
||||
https://gitlab.gnome.org/fmuellner/gjs-ci-tools.git \
|
||||
main &&
|
||||
|
||||
./.gitlab-ci/install-meson-project.sh \
|
||||
--subdir subprojects/extensions-tool/ \
|
||||
--prepare ./generate-translations.sh \
|
||||
@ -126,12 +129,10 @@ js_check:
|
||||
- .gnome-shell.fedora
|
||||
stage: review
|
||||
script:
|
||||
- find js -name '*.js' $(printf "! -wholename %s " $(cat .jscheckignore)) -exec js102 -c '{}' ';' 2>&1 | tee $JS_LOG
|
||||
- (! grep -q . $JS_LOG)
|
||||
- gjs-check-syntax
|
||||
artifacts:
|
||||
paths:
|
||||
- ${JS_LOG}
|
||||
when: on_failure
|
||||
reports:
|
||||
junit: gjs-check-syntax.junit.xml
|
||||
|
||||
eslint:
|
||||
extends:
|
||||
@ -177,7 +178,10 @@ potfile_js_check:
|
||||
- .gnome-shell.fedora
|
||||
stage: review
|
||||
script:
|
||||
- js102 -m .gitlab-ci/check-potfiles.js
|
||||
- gjs-check-potfiles
|
||||
artifacts:
|
||||
reports:
|
||||
junit: gjs-check-potfiles.junit.xml
|
||||
|
||||
build:
|
||||
stage: build
|
||||
|
@ -1,207 +0,0 @@
|
||||
const gettextFuncs = new Set([
|
||||
'_',
|
||||
'N_',
|
||||
'C_',
|
||||
'NC_',
|
||||
'dcgettext',
|
||||
'dgettext',
|
||||
'dngettext',
|
||||
'dpgettext',
|
||||
'gettext',
|
||||
'ngettext',
|
||||
'pgettext',
|
||||
]);
|
||||
|
||||
function dirname(file) {
|
||||
const split = file.split('/');
|
||||
split.pop();
|
||||
return split.join('/');
|
||||
}
|
||||
|
||||
const scriptDir = dirname(import.meta.url);
|
||||
const root = dirname(scriptDir);
|
||||
|
||||
const excludedFiles = new Set();
|
||||
const foundFiles = new Set()
|
||||
|
||||
function addExcludes(filename) {
|
||||
const contents = os.file.readFile(filename);
|
||||
const lines = contents.split('\n')
|
||||
.filter(l => l && !l.startsWith('#'));
|
||||
lines.forEach(line => excludedFiles.add(line));
|
||||
}
|
||||
|
||||
addExcludes(`${root}/po/POTFILES.in`);
|
||||
addExcludes(`${root}/po/POTFILES.skip`);
|
||||
|
||||
function walkAst(node, func) {
|
||||
func(node);
|
||||
nodesToWalk(node).forEach(n => walkAst(n, func));
|
||||
}
|
||||
|
||||
function findGettextCalls(node) {
|
||||
switch(node.type) {
|
||||
case 'CallExpression':
|
||||
if (node.callee.type === 'Identifier' &&
|
||||
gettextFuncs.has(node.callee.name))
|
||||
throw new Error();
|
||||
if (node.callee.type === 'MemberExpression' &&
|
||||
node.callee.object.type === 'Identifier' &&
|
||||
node.callee.object.name === 'Gettext' &&
|
||||
node.callee.property.type === 'Identifier' &&
|
||||
gettextFuncs.has(node.callee.property.name))
|
||||
throw new Error();
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function nodesToWalk(node) {
|
||||
switch(node.type) {
|
||||
case 'ArrayPattern':
|
||||
case 'BreakStatement':
|
||||
case 'CallSiteObject': // i.e. strings passed to template
|
||||
case 'ContinueStatement':
|
||||
case 'DebuggerStatement':
|
||||
case 'EmptyStatement':
|
||||
case 'Identifier':
|
||||
case 'Literal':
|
||||
case 'MetaProperty': // i.e. new.target
|
||||
case 'Super':
|
||||
case 'ThisExpression':
|
||||
return [];
|
||||
case 'ArrowFunctionExpression':
|
||||
case 'FunctionDeclaration':
|
||||
case 'FunctionExpression':
|
||||
return [...node.defaults, node.body].filter(n => !!n);
|
||||
case 'AssignmentExpression':
|
||||
case 'BinaryExpression':
|
||||
case 'ComprehensionBlock':
|
||||
case 'LogicalExpression':
|
||||
return [node.left, node.right];
|
||||
case 'ArrayExpression':
|
||||
case 'TemplateLiteral':
|
||||
return node.elements.filter(n => !!n);
|
||||
case 'BlockStatement':
|
||||
case 'Program':
|
||||
return node.body;
|
||||
case 'StaticClassBlock':
|
||||
return [node.body];
|
||||
case 'ClassField':
|
||||
return [node.name, node.init];
|
||||
case 'CallExpression':
|
||||
case 'NewExpression':
|
||||
case 'OptionalCallExpression':
|
||||
case 'TaggedTemplate':
|
||||
return [node.callee, ...node.arguments];
|
||||
case 'CatchClause':
|
||||
return [node.body, node.guard].filter(n => !!n);
|
||||
case 'ClassExpression':
|
||||
case 'ClassStatement':
|
||||
return [...node.body, node.superClass].filter(n => !!n);
|
||||
case 'ClassMethod':
|
||||
return [node.name, node.body];
|
||||
case 'ComprehensionExpression':
|
||||
case 'GeneratorExpression':
|
||||
return [node.body, ...node.blocks, node.filter].filter(n => !!n);
|
||||
case 'ComprehensionIf':
|
||||
return [node.test];
|
||||
case 'ComputedName':
|
||||
return [node.name];
|
||||
case 'ConditionalExpression':
|
||||
case 'IfStatement':
|
||||
return [node.test, node.consequent, node.alternate].filter(n => !!n);
|
||||
case 'DoWhileStatement':
|
||||
case 'WhileStatement':
|
||||
return [node.body, node.test];
|
||||
case 'ExportDeclaration':
|
||||
return [node.declaration, node.source].filter(n => !!n);
|
||||
case 'ImportDeclaration':
|
||||
return [...node.specifiers, node.source];
|
||||
case 'LetStatement':
|
||||
return [...node.head, node.body];
|
||||
case 'ExpressionStatement':
|
||||
return [node.expression];
|
||||
case 'ForInStatement':
|
||||
case 'ForOfStatement':
|
||||
return [node.body, node.left, node.right];
|
||||
case 'ForStatement':
|
||||
return [node.init, node.test, node.update, node.body].filter(n => !!n);
|
||||
case 'LabeledStatement':
|
||||
return [node.body];
|
||||
case 'MemberExpression':
|
||||
return [node.object, node.property];
|
||||
case 'ObjectExpression':
|
||||
case 'ObjectPattern':
|
||||
return node.properties;
|
||||
case 'OptionalExpression':
|
||||
return [node.expression];
|
||||
case 'OptionalMemberExpression':
|
||||
return [node.object, node.property];
|
||||
case 'Property':
|
||||
case 'PrototypeMutation':
|
||||
return [node.value];
|
||||
case 'ReturnStatement':
|
||||
case 'ThrowStatement':
|
||||
case 'UnaryExpression':
|
||||
case 'UpdateExpression':
|
||||
case 'YieldExpression':
|
||||
return node.argument ? [node.argument] : [];
|
||||
case 'SequenceExpression':
|
||||
return node.expressions;
|
||||
case 'SpreadExpression':
|
||||
return [node.expression];
|
||||
case 'SwitchCase':
|
||||
return [node.test, ...node.consequent].filter(n => !!n);
|
||||
case 'SwitchStatement':
|
||||
return [node.discriminant, ...node.cases];
|
||||
case 'TryStatement':
|
||||
return [node.block, node.handler, node.finalizer].filter(n => !!n);
|
||||
case 'VariableDeclaration':
|
||||
return node.declarations;
|
||||
case 'VariableDeclarator':
|
||||
return node.init ? [node.init] : [];
|
||||
case 'WithStatement':
|
||||
return [node.object, node.body];
|
||||
default:
|
||||
print(`Ignoring ${node.type}, you should probably fix this in the script`);
|
||||
}
|
||||
}
|
||||
|
||||
function walkDir(dir) {
|
||||
os.file.listDir(dir).forEach(child => {
|
||||
if (child.startsWith('.'))
|
||||
return;
|
||||
|
||||
const path = os.path.join(dir, child);
|
||||
const relativePath = path.replace(`${root}/`, '');
|
||||
if (excludedFiles.has(relativePath))
|
||||
return;
|
||||
|
||||
if (!child.endsWith('.js')) {
|
||||
try {
|
||||
walkDir(path);
|
||||
} catch (e) {
|
||||
// not a directory
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const script = os.file.readFile(path);
|
||||
const ast = Reflect.parse(script);
|
||||
walkAst(ast, findGettextCalls);
|
||||
} catch (e) {
|
||||
foundFiles.add(path);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
walkDir(root);
|
||||
|
||||
if (foundFiles.size === 0)
|
||||
quit(0);
|
||||
|
||||
print('The following files are missing from po/POTFILES.in:')
|
||||
foundFiles.forEach(f => print(` ${f}`));
|
||||
quit(1);
|
Loading…
Reference in New Issue
Block a user